Gestion simplifiée des requettes HTTP avec Axios
Axios est une bibliothèque JavaScript populaire qui facilite les requêtes HTTP. Bien qu’il soit possible d’effectuer des requêtes avec l’API native fetch
, Axios offre plusieurs fonctionnalités qui simplifient et enrichissent cette expérience.
1. Simplification de la syntaxe
Axios simplifie les requêtes HTTP en gérant automatiquement la conversion des données JSON et en nécessitant moins de configuration pour les options.
Dans les exemples ci-dessous, je m'autorise des any
pour ne pas alourdir trop le code pour les exemples avec fetch
. Par contre pour Axios
je mets les types.
Avec fetch
:
import React, { useEffect, useState } from 'react';
const FetchExample: React.FC = () => {
const [data, setData] = useState<any>(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
};
fetchData().catch(console.error);
}, []);
return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
};
export default FetchExample;
Avec Axios :
import React, { useEffect, useState } from "react";
import axios from "axios";
// Définition de l'interface correspondant à la structure de la réponse API
type Post = {
userId: number;
id: number;
title: string;
body: string;
}
const AxiosExample: React.FC = () => {
const [data, setData] = useState<Post | null>(null);
//un seul appel à l'API au chargement du composant
useEffect(() => {
axios
.get<Post>("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => setData(response.data))
.catch(console.error);
}, []);
return (
<div>
{data ? (
<div>
<h3>{data.title}</h3>
<p>{data.body}</p>
<small>By user {data.userId}</small>
</div>
) : (
"Loading..."
)}
</div>
);
};
export default AxiosExample;
👉 Avantage : Axios gère automatiquement les erreurs HTTP (response.ok
) et parse automatiquement le JSON. Cela rend le code plus concis.
2. Gestion des erreurs simplifiée
Axios inclut un gestionnaire d'erreurs intégré, tandis qu'avec fetch
, il faut manuellement vérifier le statut de la réponse if (!response.ok)
.
Avec fetch
:
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!response.ok) throw new Error(`Error: ${response.status}`);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
};
Avec Axios :
import React, { useEffect, useState } from "react";
import axios from "axios";
// Définition de l'interface correspondant à la structure de la réponse API
type Post = {
userId: number;
id: number;
title: string;
body: string;
}
const AxiosExample2: React.FC = () => {
const [data, setData] = useState<Post | null>(null);
const fetchData = async (): Promise<void> => {
try {
//on demande un id qui n'existe pas
const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts/-2');
setData(data);
} catch (error) {
//vous devriez voir l'erreur :
//Axios error: Request failed with status code 404
//fetchData @ AXiosExample2.tsx:21
if (axios.isAxiosError(error)) {
console.error('Axios error:', error.message);
} else {
console.error('Unexpected error:', error);
}
}
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
{data ? (
<div>
<h3>{data.title}</h3>
<p>{data.body}</p>
<small>By user {data.userId}</small>
</div>
) : (
"Loading..."
)}
</div>
);
};
export default AxiosExample2;
👉 Différence : Axios fournit un utilitaire (axios.isAxiosError
) pour mieux diagnostiquer les erreurs et les différencier des erreurs natives.
useEffect
Dans mon exemple
useEffect(() => {
fetchData();
}, []);
fetchData
est appelée mais son résultat n'est pas attendu dans useEffect
. fetchdata
retourne une promesse mais useEffect
n'attend pas la résolution de cette promesse.
En fait, pour React la fonction que l'on passe à useEffect
est une fonction synchrone. Le code qui est écrit juste avant est totalement équivalent à :
useEffect(() => {
() => {
fetchData();
}
}, []);
Si on avait écrit le code suivant. Une erreur serait levée par React.
useEffect(() => {
await fetchData();
//autre code à éxécuter uniquement quand fetchData est terminé
}, []);
La fonction que passée à useEffect
est une fonction d'effet synchrone. React attend qu'elle retourne undefined
.
Une fonction async
retourne toujours une Promise, ce qui n'est pas compatible avec l'attente de React. useEffect
est conçu pour exécuter des effets après le rendu (comme la récupération de données ou l'ajout d'écouteurs d'événements). Si la fonction retournait une Promise
, React ne saurait pas gérer le comportement asynchrone proprement dans le cycle de vie du composant.
Pour exécuter du code asynchrone dans useEffect, la meilleure pratique consiste à déclarer une fonction interne, comme ceci :
useEffect(() => {
const fetchDataAndHandle = async () => {
await fetchData();
// Autres actions après la résolution
};
fetchDataAndHandle(); // Appel de la fonction asynchrone
}, []);
3. Intercepteurs pour les requêtes et réponses
Les intercepteurs permettent d’appliquer automatiquement des actions avant qu'une requête soit envoyée ou après qu’une réponse soit reçue, sans avoir à répliquer cette logique dans chaque appel d’API. Cela est particulièrement utile dans des cas comme :
- Ajouter automatiquement des headers d’autorisation ou d'autres configurations à toutes les requêtes.
- Gérer les erreurs globalement (redirections en cas d’échec d’authentification, journaux de débogage...)
- Appliquer une logique uniforme sur les réponses ou les requêtes.
Ci-dessous un code illustrant l'intérêt des intercepteurs.
vous remarquerez que ce code n'est pas inclus dans un composant.
C'est une bonne pratique. La logique de l'appel à une API et la réception des données doit se faire dans des fichiers dédiés. Par contre l'utilisation des données doit bien être dans un composant (génération via jsx
)
import axios from 'axios';
// Créer une instance Axios
const api = axios.create({
baseURL: 'https://api.example.com',
});
// Ajouter un intercepteur pour la préparation des requêtes
api.interceptors.request.use(
config => {
const token = localStorage.getItem('authToken'); // Récupérer un token dynamiquement
// Récupérer un token dynamiquement (cf https://js-but1.codenestedu.fr/docs/bonus/token pour comprendre
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
console.log('Request config:', config);
return config;
},
error => {
// Gérer les erreurs avant que la requête ne soit envoyée
console.error('Request error:', error);
return Promise.reject(error);
}
);
// Ajouter un intercepteur pour les réponses
api.interceptors.response.use(
response => response, // Retourner directement les réponses réussies
async error => {
if (error.response) {
if (error.response.status === 401) {
console.warn('401 Unauthorized detected. Attempting token refresh...');
try {
// Appeler une route pour rafraîchir le token
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
const { data } = await api.post('/auth/refresh-token', {
refreshToken,
});
// Stocker le nouveau token
localStorage.setItem('authToken', data.token);
// Réessayer la requête originale avec le nouveau token
error.config.headers.Authorization = `Bearer ${data.token}`;
return api.request(error.config); // Relancer la requête initiale
} else {
console.error('No refresh token available. Redirecting to login...');
window.location.href = '/login'; // Rediriger l'utilisateur
}
} catch (refreshError) {
console.error('Token refresh failed:', refreshError);
window.location.href = '/login'; // Rediriger si le rafraîchissement échoue
}
} else {
console.error('Response error:', error.response.status, error.message);
}
} else {
console.error('Unexpected error:', error.message);
}
return Promise.reject(error); // Propager l'erreur pour un traitement local si nécessaire
}
);
// Exemple d'utilisation
const fetchData = async () => {
try {
const { data } = await api.get('/data');
console.log('Fetched data:', data);
} catch (error) {
console.error('Fetch error:', error);
}
};
export default api;
4. Paramètres de requête simplifiés
L'un des avantages d'Axios par rapport à l'API native fetch
est sa gestion simplifiée des paramètres de requête dans les URL, en particulier lorsqu'on travaille avec des objets ou des ensembles complexes de données.
Avec fetch
:
const fetchData = async () => {
// Construire manuellement la query string
const query = `search=apple&category=fruit&page=1`;
// Créer un objet Request en passant l'URL avec les paramètres et la méthode GET
const myRequest = new Request(`https://api.example.com/items?${query}`, {
method: 'GET', // Spécifie que c'est une requête GET (GET est par défaut dans fetch)
headers: {
'Authorization': 'Bearer my-token', // Exemple d'en-tête d'authentification
},
});
// Utiliser fetch avec l'objet Request créé
const response = await fetch(myRequest);
const data = await response.json();
console.log(data);
};
fetchData();
Axios permet de gérer facilement les paramètres de requêtes en passant un objet params
dans l'option de configuration. Il s'occupe automatiquement de formater les clés et valeurs, d'encoder les caractères spéciaux, et d'ajouter les paramètres à l'URL.
import axios from 'axios';
const fetchData = async () => {
const response = await axios.get('https://api.example.com/items', {
params: {
search: 'apple',
category: 'fruit',
page: 1,
},
});
console.log(response.data);
};
fetchData();
Si vous voulez en savoir plus sur Axios : lien du dépôt