Accueil / Articles

Quel est le coût pour vous du fetch avec useEffect et ce que TanStack Query change

Pourquoi la récupération de données basée sur les effets entraîne des requêtes dupliquées, du code générique et des closures obsolètes dans React, et comment les fonctionnalités keys, staleTime et invalidation de TanStack Query résolvent ces problèmes.

Éditeur : REACTAPP.TOP6 min de lecture1360 mots

Sources: Stop Using useEffect for API Calls, Use React Query Instead

Récupérer des données à l’intérieur de useEffect est le premier schéma que la plupart des développeurs React apprennent, et il fonctionne bien pour une seule requête sur une seule page. À mesure que l’application grandit, ce même schéma multiplie discrètement les requêtes, répète du code générique dans chaque composant et entraîne des bugs de dépendance subtils. Cet article explique d’où proviennent ces problèmes et comment TanStack Query (anciennement React Query) y remédie, afin que vous puissiez décider quand passer à cette solution et ce qu’il faut configurer une fois le changement effectué.

Le schéma de base utilisé par tous

La structure courante consiste en un effet disposant d’un tableau de dépendances vide, qui appelle une fonction fetch lors du montage du composant :

useEffect(() => {
  fetchData();
}, []);

Cela semble simple, et c’est justement là le problème. Le code ne mentionne rien concernant le cache, la suppression des doublons, les tentatives répétées, l’annulation ou ce qui se passe lorsque le composant est remonté. Chacun de ces manques se transforme en bug ou en requête supplémentaire lorsque l’on doit gérer des dizaines d’extrémités et de nombreux états de chargement et d’erreur.

Pourquoi la requête est envoyée deux fois, et le véritable problème derrière

La première surprise fréquente est de voir la même requête apparaître deux fois dans l’onglet réseau lors du chargement de la page. En mode développement, StrictMode de React monte, démonte et remonte délibérément les composants afin de mettre en évidence des effets qui ne se nettoient pas eux-mêmes. La requête dupliquée est intentionnelle en mode développement et ne se produit pas dans une version de production.

La double requête est un symptôme d’un problème réel en environnement de production : la récupération de données basée sur des effets est liée au cycle de vie du composant. Chaque chargement déclenche une nouvelle requête, même lorsque les données sont déjà disponibles. Les composants qui se chargent à plusieurs reprises, tels que les panneaux de tab, les modaux et les lignes de liste, continuent d’appeler le backend, ce qui augmente la charge serveur et rend l’interface plus lente qu’elle ne le devrait.

TanStack Query sépare les données du composant. Les résultats sont stockés dans un cache partagé sous une clé de requête. Lorsqu’un autre composant, ou le même après un nouveau chargement, demande des données encore considérées comme fraîches, la bibliothèque renvoie la valeur en cache sans avoir à utiliser le réseau. Plusieurs composants demandant la même clé au même moment partagent également une seule requête en cours d’exécution.

Gestion manuelle de l’état de chargement et d’erreur

Avec useEffect, chaque requête nécessite son propre ensemble de trois variables d’état pour les données, le indicateur de chargement et l’erreur :

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

En répétant ce processus pour chaque appel API, on obtient beaucoup de codes presque identiques, chacun présentant un risque d’erreur. Le problème le plus courant est d’oublier de réinitialiser loading dans le cas d’une erreur, ce qui fait que l’indicateur de chargement ne s’arrête jamais. D’autres erreurs incluent le fait de ne pas ignorer les réponses arrivées après que le composant ait été démonté, ou de permettre à une réponse lente reçue précédemment d’écraser une réponse plus récente lorsque les entrées changent rapidement.

useQuery renvoie directement data, error, isLoading et isError (ainsi que des champs d’état plus détaillés), et il gère à votre place les réponses hors ordre. Le code générique disparaît des composants, et les cas limites sont résolus une seule fois dans une bibliothèque bien testée plutôt que plusieurs fois dans le code de l’application.

Arrays de dépendances versus clés de requête

Les effets dépendent du bon fonctionnement des arrays de dépendances. Si vous omettez une valeur que l’effet lit, vous obtenez une fermeture obsolète qui effectue des requêtes avec des paramètres dépassés, ce qui peut entraîner des bugs difficiles à identifier. Si vous incluez un objet ou une fonction qui est recréé à chaque rendu, l’effet s’exécute à chaque rendu, envoyant une requête à chaque fois.

TanStack Query remplace cette méthode de devinette par un contrat plus clair : vous attribuez à chaque requête une clé et une fonction de récupération. La clé doit inclure toutes les variables sur lesquelles la demande dépend, par exemple le nom de la ressource ainsi qu’un ID d’utilisateur ou un filtre. Lorsque la clé change, une nouvelle requête est exécutée ; si elle reste identique, les données en cache sont réutilisées. Les clés sont comparées selon leur contenu et non par référence, de sorte que la création d’un nouvel array à chaque rendu ne provoque pas de requêtes supplémentaires. La récupération des données est alors gérée par des politiques explicites, telles que le montage d’un composant, la récupération du focus de la fenêtre ou la réconnexion au réseau, en combinaison avec le paramètre staleTime que vous configurez.

Vous devez toujours prendre en compte les dépendances, mais vous les exprimez une seule fois dans une clé au lieu de gérer un array qui doit rester synchronisé avec le corps de l’effet.

Caché, staleTime et invalidation

Trois fonctionnalités expliquent la majeure partie de la différence dans le travail quotidien.

Le cache

Lorsque les données d’une clé sont récupérées, elles restent dans le cache. Lorsque l’utilisateur quitte la page puis y revient, le résultat en cache s’affiche immédiatement ; si les données sont obsolètes, la bibliothèque les récupère à nouveau en arrière-plan et met à jour l’écran dès l’arrivée de la nouvelle réponse.

staleTime

staleTime détermine pendant combien de temps les données sont considérées comme fraîches. Tant qu’elles le sont, les rechargements et les nouveaux abonnés ne déclenchent absolument aucune demande. La valeur par défaut est zéro, ce qui signifie que les données sont considérées comme obsolètes immédiatement ; les données en cache s’affichent toujours instantanément, mais une récupération en arrière-plan a lieu lors du prochain rechargement ou du passage en premier plan de la fenêtre. Définir une valeur raisonnable pour staleTime pour des données qui ne changent pas toutes les secondes permet souvent de réduire considérablement le trafic vers le serveur.

invalidateQueries

Après une mutation, telle que la mise à jour d’un profil ou la création d’une publication, vous appelez queryClient.invalidateQueries avec la clé correspondante. Les requêtes qui correspondent sont marquées comme obsolètes et celles qui sont actuellement en usage se rechargent automatiquement. Il n’y a pas de rechargement manuel après chaque mutation, ni de copie manuelle des réponses du serveur dans l’état local.

Ces trois éléments ensemble couvrent la plupart des problèmes causés par le chargement basé sur les effets. Pour en savoir plus sur l’organisation des clés, des options de requête et des mutations à grande échelle, consultez notre guide sur la structuration d’une couche de données TanStack Query.

Un scénario de migration typique

Considérez un tableau de bord SaaS de Next.js avec plusieurs onglets, chacun chargant ses propres données via useEffect. Comme le changement d’onglet rémonte les panneaux, chaque changement d’onglet envoie une nouvelle série de requêtes. Le backend reçoit donc bien plus de trafic que nécessaire, ce qui provoque des temps de chargement lents pour les utilisateurs.

Une migration suit généralement trois étapes :

  1. Remplacer chaque effet par useQuery ainsi que par une clé qui capture tous les paramètres de la requête.
  2. Attribuer à chaque requête une valeur staleTime reflétant la fréquence réelle de modification de ses données.
  3. Après chaque mutation, invalider les clés dont les données sont affectées.

Le résultat escompté est un tableau de bord plus fluide, moins d’appels au backend et une meilleure expérience pour les utilisateurs, avec moins de code dans chaque composant.

Où useEffect reste pertinent

Ce n’est pas le cas de supprimer complètement useEffect. Il reste l’outil idéal pour synchroniser avec des éléments extérieurs à React qui ne relèvent pas de données de type demande-réponse : abonnements, écouteurs d’événements DOM, compteurs à retardement et widgets de tiers. Cependant, pour les appels API dans une application en développement, une bibliothèque dédiée à l’état du serveur est plus adaptée, car elle réduit la charge du backend, élimine le code générique et offre du cacheage ainsi que de l’invalidation intégrés.

Il existe aussi des cas où une bibliothèque est plus que nécessaire, comme pour une petite application avec une ou deux requêtes. Dans de tels cas, un petit crochet personnalisé doté d’une fonction de suppression appropriée peut suffire ; notre article sur un crochet useFetch minimal et ses limites aborde ce compromis. Si vous n’êtes pas sûr, migrez d’abord une page et comparez le nombre de requêtes dans l’onglet réseau.

Points clés

  • La double requête en développement provient de StrictMode ; le problème en production est que les fetch basés sur des effets se répètent à chaque montage.
  • Une clé de requête remplace le tableau des dépendances : incluez toutes les entrées sur lesquelles la requête dépend, et le cacheage ainsi que le refetch s’ensuivent automatiquement.
  • La valeur par défaut de staleTime est zéro, donc définissez-la intentionnellement si vous souhaitez éviter des refetch en arrière-plan lors du remontage.
  • Invalidez les clés associées après les mutations au lieu de récupérer ou synchroniser manuellement l’état.
  • Utilisez useEffect pour la synchronisation avec des systèmes externes, et non pour charger les données du serveur.