
React Query (TanStack Query) : Caching, synchronisation, mises à jour en arrière-plan
Explorez React Query (TanStack Query), la bibliothèque React pour le data fetching et la gestion d'état serveur, offrant caching, synchronisation et mises à jour optimisées.
Au-delà du simple fetching : Gérer l'état serveur
React Query, désormais officiellement intégré dans la suite TanStack Query (qui supporte d'autres frameworks comme Solid, Vue, Svelte), est souvent considéré comme la bibliothèque de référence pour la gestion des données asynchrones dans les applications React. Elle va bien au-delà du simple data fetching en se positionnant comme une solution complète pour la gestion de l'état serveur (server state).
La philosophie clé de React Query est que l'état serveur est fondamentalement différent de l'état client :
- Il est stocké à distance et nécessite des opérations asynchrones pour être lu ou mis à jour.
- Il est partagé par plusieurs utilisateurs et peut être modifié par d'autres sans que votre client ne le sache directement.
- Il peut devenir obsolète (stale) rapidement.
React Query prend en charge toute la complexité liée à la gestion de cet état serveur : mise en cache, déduplication des requêtes, mises à jour en arrière-plan, gestion des données périmées, etc., vous permettant de traiter les données serveur presque aussi facilement que l'état client local.
Le hook `useQuery` : Le pilier de la récupération de données
Le principal outil pour récupérer des données avec React Query est le hook `useQuery`. Sa signature de base est :
const { data, error, status, isLoading, isFetching, ... } = useQuery({ queryKey, queryFn, options });- `queryKey` (obligatoire) : C'est la clé unique qui identifie cette requête et ses données dans le cache. C'est généralement un tableau. Le premier élément est souvent une chaîne décrivant le type de données (ex: `['todos']`), et les éléments suivants peuvent être des variables ou des objets qui rendent la requête unique (ex: `['todos', { status: 'done', page: 2 }]`, `['user', userId]`). React Query sérialise ce tableau de manière stable pour l'utiliser comme clé de cache. Si un élément de la clé change, la requête est automatiquement relancée.
- `queryFn` (obligatoire) : Une fonction asynchrone qui effectue la récupération réelle des données (en utilisant `fetch`, `axios`, etc.). Elle doit retourner une promesse qui se résout avec les données ou lève une erreur en cas d'échec. Elle reçoit un objet contenant le `queryKey` en argument, ce qui est utile pour passer des paramètres à l'appel API.
- `options` (optionnel) : Un objet de configuration très riche permettant de personnaliser le comportement : `staleTime`, `cacheTime`, `enabled` (pour le fetching conditionnel), `retry`, `onSuccess`, `onError`, etc.
Le hook `useQuery` retourne un objet contenant de nombreux états décrivant le cycle de vie de la requête :
- `data` : Les dernières données récupérées avec succès. `undefined` initialement ou si une erreur est survenue.
- `error` : L'objet d'erreur si la dernière tentative de requête a échoué.
- `status` : Une chaîne indiquant l'état global : `'loading'` (première requête, pas de données), `'error'` (la requête a échoué), `'success'` (la requête a réussi).
- `isLoading` : Alias pour `status === 'loading'`. Vrai uniquement lors du chargement initial sans données en cache.
- `isFetching` : Booléen indiquant si une requête est actuellement en cours d'exécution en arrière-plan (y compris les revalidations automatiques). Utile pour afficher des indicateurs de chargement non bloquants.
- Et bien d'autres : `isSuccess`, `isError`, `isStale`, `refetch`, etc.
Exemple d'utilisation de `useQuery`
import React from 'react';
import { useQuery } from '@tanstack/react-query'; // Importer depuis @tanstack/react-query
import axios from 'axios';
// La fonction qui effectue le fetch réel
const fetchTodos = async ({ queryKey }) => {
// eslint-disable-next-line no-unused-vars
const [_key, { status }] = queryKey; // Déstructure le queryKey si besoin
const params = status ? { status } : {};
const { data } = await axios.get('/api/todos', { params });
return data;
};
function TodoList({ filterStatus }) {
// Définir le queryKey. Il inclut filterStatus pour que la requête se relance si le filtre change.
const queryKey = ['todos', { status: filterStatus }];
const { data: todos, error, isLoading, isFetching } = useQuery({
queryKey: queryKey,
queryFn: fetchTodos,
staleTime: 5 * 60 * 1000, // Considérer les données comme fraîches pendant 5 minutes
// enabled: !!filterStatus // Exemple: Ne lancer la requête que si filterStatus est défini
});
return (
Liste des tâches {isFetching ? '(Rafraîchissement...)' : ''}
{isLoading && Chargement initial...
}
{error && Erreur : {error.message}
}
{todos && (
{todos.map(todo => (
- {todo.title}
))}
)}
);
}
export default TodoList;
Notez l'utilisation de `isLoading` pour le chargement initial et `isFetching` pour les revalidations en arrière-plan.
Caching intelligent et gestion des données périmées (Stale Data)
React Query met agressivement en cache les données basées sur leur `queryKey`. Par défaut, dès qu'une requête réussit, ses données sont considérées comme "périmées" (stale). Cela ne signifie pas qu'elles sont mauvaises, mais que React Query pourrait vouloir les revalider la prochaine fois qu'elles seront nécessaires (par exemple, au montage d'un nouveau composant utilisant la même clé, ou lors d'un focus de fenêtre).
Vous pouvez contrôler ce comportement avec l'option `staleTime`. C'est la durée (en millisecondes) pendant laquelle les données sont considérées comme "fraîches". Pendant ce temps, React Query servira directement les données du cache sans déclencher de nouvelle requête réseau.
Indépendamment de `staleTime`, les données restent dans le cache pendant une durée définie par `cacheTime` (par défaut 5 minutes d'inactivité). Si un composant utilisant une clé est démonté, les données restent en cache pendant `cacheTime`. Si un autre composant utilisant la même clé est remonté avant l'expiration du `cacheTime`, les données (même si "stale") sont servies immédiatement depuis le cache, et une revalidation en arrière-plan est déclenchée si nécessaire (si `staleTime` est dépassé).
Mises à jour en arrière-plan et synchronisation
React Query excelle dans le maintien de la fraîcheur des données sans bloquer l'interface utilisateur. Il déclenche automatiquement des revalidations en arrière-plan (background refetches) dans diverses situations :
- Au montage d'un nouveau composant utilisant une clé dont les données sont périmées.
- Lorsque la fenêtre du navigateur regagne le focus (configurable avec `refetchOnWindowFocus`).
- Lorsque le réseau est reconnecté après une interruption (configurable avec `refetchOnReconnect`).
- Optionnellement, à intervalle régulier (configurable avec `refetchInterval`).
Pendant ces revalidations, l'état `isFetching` passe à `true`, mais `data` contient toujours les données précédentes (celles du cache), ce qui permet de garder une interface fonctionnelle. Une fois la revalidation terminée, `data` est mise à jour avec les nouvelles informations et `isFetching` repasse à `false`.
De plus, si plusieurs composants dans votre application utilisent `useQuery` avec la même `queryKey`, React Query gère automatiquement la synchronisation. Ils partagent tous la même entrée de cache. Une seule requête réseau est effectuée (déduplication), et tous les composants sont mis à jour simultanément lorsque les données changent.
Conclusion : Puissance et robustesse pour l'état serveur
React Query (TanStack Query) est une solution extrêmement puissante qui simplifie radicalement la gestion de l'état serveur dans les applications React.
- Réduit le boilerplate : Elimine le besoin d'écrire manuellement la logique de fetch, de gestion d'état (loading/error), de caching, d'annulation.
- Améliore l'UX : Fournit des données rapidement grâce au cache, met à jour en arrière-plan sans bloquer l'UI.
- Performances optimisées : Caching intelligent, déduplication des requêtes, re-rendus minimaux.
- Robustesse : Gestion intégrée des erreurs, des retries, de l'annulation.
- Outils puissants : Inclut des hooks pour les mutations (`useMutation`), la pagination (`useInfiniteQuery`) et d'excellents DevTools pour le débogage.
Son adoption représente souvent un gain majeur en productivité et en qualité pour les applications qui dépendent fortement d'interactions API. Bien qu'elle introduise ses propres concepts (queryKey, staleTime, cacheTime), l'investissement pour les apprendre est rapidement rentabilisé par les bénéfices qu'elle apporte.