Pour récupérer des données en React, lancez la requête dans useEffect, enregistrez la réponse dans l'état et affichez un message de chargement jusqu'à son arrivée. Placez les valeurs dont la requête dépend, comme un identifiant, dans le tableau de dépendances, pour que l'effet relance la récupération quand elles changent.
Les exemples de cette page ne peuvent pas accéder au réseau, donc fetchUser est une fausse API : une promesse qui se résout après un délai, comme le fait fetch. Remplacez 800 par 3000 pour voir le texte de chargement rester plus longtemps.
Avec une vraie API, le corps de l'effet ressemble à ceci :
useEffect(() => {
fetch('https://api.example.com/users/1')
.then((res) => res.json())
.then((data) => setUser(data));
}, []);
Chargement, erreur et données
Une requête a trois issues que l'écran doit montrer : encore en chargement, échec, ou terminé. Gardez chacune dans l'état, et réinitialisez-les quand l'effet lance une nouvelle requête.
Cliquez sur User 3 : la fausse API rejette, le message d'erreur remplace le nom, et la console affiche l'erreur. Le bloc finally désactive loading dans les deux cas, pour qu'une requête échouée ne laisse jamais l'indicateur affiché.
Quand fetch communique avec un vrai serveur, une réponse 404 ou 500 ne rejette pas la promesse. Vérifiez res.ok et levez l'erreur vous-même :
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
async/await dans un effet
La fonction que vous passez à useEffect ne peut pas être async. React attend qu'elle ne renvoie rien ou renvoie une fonction de nettoyage, et une fonction async renvoie toujours une promesse.
// Wrong: the effect returns a promise
useEffect(async () => {
const data = await fetchUser(id);
setUser(data);
}, [id]);
// Right: define an async function inside and call it
useEffect(() => {
async function load() {
const data = await fetchUser(id);
setUser(data);
}
load();
}, [id]);
Conditions de course
Quand l'identifiant change vite, deux requêtes sont en cours en même temps. Rien ne garantit qu'elles répondent dans l'ordre. Si la plus ancienne est plus lente, elle arrive en dernier et écrase les données de l'identifiant que l'utilisateur a choisi le plus récemment.
La solution est un indicateur ignore. Chaque exécution de l'effet a son propre indicateur, et son nettoyage le passe à true. Comme React exécute le nettoyage avant l'exécution suivante, une réponse d'une exécution périmée voit ignore === true et est abandonnée. Ce bloc affiche deux fois le même profil, sans et avec l'indicateur :
Cliquez sur le bouton et attendez. La réponse rapide de l'utilisateur 2 arrive en premier, donc les deux profils l'affichent et gardent Grace. Environ une seconde et demie après le clic, la réponse lente de l'utilisateur 1 arrive : le profil sans indicateur passe à Ada alors que l'utilisateur 2 est sélectionné, tandis que celui avec l'indicateur affiche dropped stale Ada et garde Grace.
Le même bug apparaît dans les champs de recherche, les onglets et toute liste filtrée par le serveur. L'exemple de chargement ci-dessus omet l'indicateur pour rester court, mais chaque récupération dans un effet devrait l'avoir, ou utiliser l'annulation décrite ci-dessous.
Annuler avec AbortController
L'indicateur ignore abandonne une réponse périmée, mais la requête va quand même jusqu'au bout. Avec le vrai fetch, vous pouvez l'annuler. Créez un AbortController dans l'effet, passez son signal à fetch et appelez abort() dans le nettoyage :
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const res = await fetch(`/api/users/${id}`, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setUser(await res.json());
} catch (err) {
if (err.name === 'AbortError') return; // cancelled on purpose
setError(err);
}
}
load();
return () => controller.abort();
}, [id]);
Un fetch annulé rejette avec une AbortError, donc le bloc catch l'ignore au lieu de l'afficher comme un échec. L'annulation couvre aussi le démontage : quand le composant est retiré, son nettoyage annule la requête.
La déplacer dans un hook personnalisé
Le chargement, l'erreur et la protection contre les conditions de course sont les mêmes dans chaque composant qui récupère des données. Placez-les dans un hook personnalisé et chaque composant demande ses données en une ligne.
fetchPosts est défini hors du composant, son identité ne change donc jamais et il peut figurer sans risque dans le tableau de dépendances. Défini dans App, ce serait une nouvelle fonction à chaque rendu, l'effet s'exécuterait après chaque rendu, et comme l'effet modifie l'état, il ne cesserait jamais de récupérer des données.
Remplacez 500 par 2000 et cliquez deux fois de suite sur le bouton : la console affiche deux requêtes, et seuls les articles du dernier sujet choisi apparaissent.
Récupérer des données dans un gestionnaire d'événement
Un effet sert aux données dont le composant a besoin parce qu'il est à l'écran : une page de profil charge son profil. Quand une requête a lieu parce que l'utilisateur a fait quelque chose, comme appuyer sur Rechercher ou Enregistrer, faites-la dans le gestionnaire d'événement. Vous savez exactement ce qui l'a déclenchée, et rien ne se relance quand un état sans rapport change.
async function handleSubmit(e) {
e.preventDefault();
setStatus('saving');
const res = await fetch('/api/notes', { method: 'POST', body: JSON.stringify({ text }) });
setStatus(res.ok ? 'saved' : 'error');
}
Erreurs courantes
Des dépendances manquantes. Un effet qui lit id mais a [] récupère le premier utilisateur pour toujours. Listez chaque valeur utilisée par la requête.
Récupérer des données dans le corps du composant. Un fetch hors d'un effet s'exécute à chaque rendu, et s'il modifie l'état, il lance une boucle.
Compter sur fetch pour rejeter en cas d'erreur. Il ne rejette qu'en cas de panne réseau. Vérifiez res.ok.
Oublier de réinitialiser le chargement. Quand l'identifiant change, repassez loading à true, sinon les anciennes données restent à l'écran sans indiquer que de nouvelles arrivent.
Quand utiliser une bibliothèque ou un framework
Récupérer des données dans un effet convient pour quelques requêtes. Il n'y a pas de cache : ouvrez deux fois le même profil et il est récupéré deux fois. Les données ne sont pas partagées entre composants, il n'y a pas de nouvelle tentative en cas d'échec ni de rechargement quand l'onglet reprend le focus. Les bibliothèques le font pour vous :
import { useQuery } from '@tanstack/react-query';
function Profile({ id }) {
const { data, error, isPending } = useQuery({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then((res) => res.json()),
});
if (isPending) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;
return <p>{data.name}</p>;
}
TanStack Query et SWR sont les choix courants pour les données côté client. Les frameworks vont plus loin et chargent les données sur le serveur avant que la page n'arrive au navigateur : Next.js avec les server components, React Router avec les loaders. Cela évite le flash de chargement et la chaîne de requêtes qui démarre quand les données d'un parent doivent arriver avant qu'un enfant puisse même commencer à récupérer les siennes.
React 19 : use() avec Suspense
React 19 ajoute use, qui lit une promesse pendant le rendu. Le composant se suspend jusqu'à la résolution de la promesse, et la frontière <Suspense> la plus proche affiche un contenu de repli en attendant, si bien que le composant lui-même n'a pas d'état de chargement :
import { use, Suspense } from 'react';
function Profile({ userPromise }) {
const user = use(userPromise);
return <p>{user.name}</p>;
}
<Suspense fallback={<p>Loading...</p>}>
<Profile userPromise={userPromise} />
</Suspense>
La promesse doit être créée hors du composant (par un framework, un cache ou un parent), pas dans le rendu qui la lit, sinon chaque rendu lance une nouvelle requête. La page sur le hook use traite ce sujet avec des exemples exécutables.
Questions fréquentes
Comment récupérer des données au chargement d'un composant React ?
Lancez la requête dans un useEffect avec les valeurs dont elle dépend dans le tableau de dépendances ([] s'il n'y en a pas), et stockez le résultat dans l'état avec useState. Affichez un message de chargement jusqu'à l'arrivée des données.
Pourquoi le callback de useEffect ne peut-il pas être async ?
Une fonction async renvoie toujours une promesse, et React attend que l'effet ne renvoie rien ou renvoie une fonction de nettoyage. Écrivez une fonction async à l'intérieur de l'effet et appelez-la tout de suite.
Qu'est-ce qu'une condition de course lors de la récupération de données en React ?
Quand l'entrée change vite, deux requêtes sont en cours et la plus ancienne peut répondre en dernier, écrasant les données plus récentes. Définissez un indicateur ignore dans le nettoyage de l'effet et sautez setState quand il est actif, ou annulez la requête avec un AbortController.
Faut-il utiliser useEffect ou une bibliothèque pour récupérer des données ?
useEffect suffit pour les petites applications et vaut la peine d'être compris. Pour le cache, la déduplication, les nouvelles tentatives et le rechargement, une bibliothèque comme TanStack Query ou SWR, ou le chargement de données intégré à un framework comme Next.js, vous évite d'écrire cette logique vous-même.
Comment afficher un indicateur de chargement pendant le chargement des données ?
Gardez un booléen loading (ou une chaîne status) dans l'état, activez-le avant le début de la requête et désactivez-le quand elle se termine, en cas de succès comme d'erreur. Affichez l'indicateur tant qu'il est vrai.