Aller au contenu principal

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.

Pour une fois ...

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.

Attention subtilité de l'asynchrone et de 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 :

  1. Ajouter automatiquement des headers d’autorisation ou d'autres configurations à toutes les requêtes.
  2. Gérer les erreurs globalement (redirections en cas d’échec d’authentification, journaux de débogage...)
  3. Appliquer une logique uniforme sur les réponses ou les requêtes.
info

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)

Api.ts
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();
info

Si vous voulez en savoir plus sur Axios : lien du dépôt