Back

Utiliser TanStack Query pour une récupération de données plus intelligente dans React

Utiliser TanStack Query pour une récupération de données plus intelligente dans React

Si vous avez développé des applications React qui communiquent avec des APIs, vous connaissez le schéma : useEffect pour la récupération, useState pour les données, un autre useState pour le chargement, peut-être un de plus pour les erreurs. Avant de vous en rendre compte, vous gérez un enchevêtrement d’états juste pour afficher une liste d’utilisateurs.

Cette approche manuelle fonctionne, mais elle est fragile. Que se passe-t-il quand l’utilisateur navigue ailleurs puis revient ? Faut-il refaire la requête ? Utiliser des données obsolètes ? Qu’en est-il des nouvelles tentatives quand le réseau échoue ? Ce ne sont pas des cas limites—c’est la réalité des applications en production.

TanStack Query (anciennement React Query) résout ces problèmes en traitant l’état serveur différemment de l’état client. Au lieu de récupérer les données de manière impérative, vous déclarez ce dont vous avez besoin et laissez la bibliothèque gérer la mise en cache, la synchronisation et les mises à jour. Cet article vous montre comment passer de la récupération manuelle de données à une approche déclarative plus intelligente qui s’adapte à l’échelle.

Points clés à retenir

  • Remplacez les patterns manuels useEffect + useState par des hooks useQuery déclaratifs pour un code plus propre et maintenable
  • Tirez parti de la mise en cache automatique, de la récupération en arrière-plan et de la déduplication des requêtes pour améliorer l’expérience utilisateur
  • Utilisez les mutations avec des mises à jour optimistes pour créer des interfaces réactives qui semblent instantanées
  • Implémentez des stratégies d’invalidation de requêtes appropriées pour maintenir les données synchronisées dans votre application
  • Évitez les pièges courants comme utiliser TanStack Query pour l’état local ou oublier les paramètres dynamiques dans les clés de requête

Le problème avec la récupération manuelle de données

Voici un composant React typique qui récupère des données :

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    
    async function fetchUsers() {
      setIsLoading(true);
      setError(null);
      
      try {
        const response = await fetch('/api/users');
        if (!response.ok) throw new Error('Failed to fetch');
        
        const data = await response.json();
        if (!cancelled) {
          setUsers(data);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false);
        }
      }
    }
    
    fetchUsers();
    
    return () => { cancelled = true };
  }, []);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Cela fonctionne, mais regardez ce que nous gérons :

  • Trois éléments d’état séparés
  • Une logique de nettoyage pour empêcher les mises à jour d’état sur les composants démontés
  • Pas de mise en cache—chaque montage déclenche une nouvelle récupération
  • Pas de logique de nouvelle tentative pour les requêtes échouées
  • Aucun moyen de savoir si nos données sont obsolètes

Maintenant multipliez cette complexité sur des dizaines de composants. C’est là qu’intervient TanStack Query.

Comment TanStack Query rend la récupération de données plus intelligente

TanStack Query traite l’état serveur comme un citoyen de première classe. Au lieu d’orchestrer manuellement les récupérations, vous décrivez vos exigences de données de manière déclarative :

import { useQuery } from '@tanstack/react-query';

function UserList() {
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => {
      if (!res.ok) throw new Error('Failed to fetch');
      return res.json();
    }),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Même fonctionnalité, mais remarquez ce qui manque :

  • Pas de useState pour les données, le chargement ou les erreurs
  • Pas de useEffect ou de logique de nettoyage
  • Pas de synchronisation manuelle d’état

Mais voici ce que vous gagnez :

  • Mise en cache automatique : Naviguez ailleurs et revenez, voyez des résultats instantanés
  • Récupération en arrière-plan : Les données obsolètes se mettent à jour silencieusement
  • Déduplication des requêtes : Plusieurs composants peuvent partager la même requête
  • Logique de nouvelle tentative intégrée : Les requêtes échouées réessaient automatiquement
  • Mises à jour optimistes : L’interface se met à jour avant la confirmation du serveur

Concepts fondamentaux pour une gestion intelligente des données

Requêtes : Lecture de l’état serveur

Les requêtes récupèrent et mettent en cache les données. Le hook useQuery accepte un objet de configuration avec deux propriétés essentielles :

const { data, isLoading, error } = useQuery({
  queryKey: ['todos', userId, { status: 'active' }],
  queryFn: () => fetchTodosByUser(userId, { status: 'active' }),
  staleTime: 5 * 60 * 1000, // 5 minutes
  gcTime: 10 * 60 * 1000, // 10 minutes (anciennement cacheTime)
});

Les clés de requête identifient de manière unique chaque requête. Ce sont des tableaux qui peuvent inclure :

  • Des identifiants statiques : ['users']
  • Des paramètres dynamiques : ['user', userId]
  • Des filtres complexes : ['todos', { status, page }]

Quand une partie de la clé de requête change, TanStack Query sait qu’il faut récupérer des données fraîches.

Mutations : Modification de l’état serveur

Alors que les requêtes lisent les données, les mutations les modifient :

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateTodo() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: (newTodo) => 
      fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
        headers: { 'Content-Type': 'application/json' },
      }).then(res => {
        if (!res.ok) throw new Error('Failed to create todo');
        return res.json();
      }),
    onSuccess: () => {
      // Invalider et récupérer à nouveau
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  return (
    <button 
      onClick={() => mutation.mutate({ title: 'New Todo' })}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? 'Creating...' : 'Create Todo'}
    </button>
  );
}

Les mutations gèrent le cycle de vie complet : états de chargement, gestion d’erreurs et callbacks de succès. Le callback onSuccess est parfait pour mettre à jour votre cache après les changements.

Invalidation de requête : Maintenir les données fraîches

L’invalidation marque les requêtes comme obsolètes, déclenchant des récupérations en arrière-plan :

// Invalider tout
queryClient.invalidateQueries();

// Invalider des requêtes spécifiques
queryClient.invalidateQueries({ queryKey: ['todos'] });

// Invalider avec correspondance exacte
queryClient.invalidateQueries({ 
  queryKey: ['todo', 5], 
  exact: true 
});

C’est ainsi que TanStack Query maintient votre interface synchronisée après les mutations. Changer une tâche ? Invalider la liste des tâches. Mettre à jour un utilisateur ? Invalider les données de cet utilisateur.

Configuration de TanStack Query dans votre application React

D’abord, installez le package :

npm install @tanstack/react-query

Ensuite, enveloppez votre application avec le provider QueryClient :

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1 minute
      gcTime: 5 * 60 * 1000, // 5 minutes (anciennement cacheTime)
      retry: 3,
      retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

Le QueryClient gère toute la mise en cache et la synchronisation. Vous créez généralement une instance pour toute votre application.

Patterns avancés pour les applications en production

Mises à jour optimistes

Mettez à jour l’interface immédiatement, puis synchronisez avec le serveur :

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // Annuler les récupérations sortantes
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    
    // Instantané de la valeur précédente
    const previousTodos = queryClient.getQueryData(['todos']);
    
    // Mise à jour optimiste
    queryClient.setQueryData(['todos'], old => 
      old ? [...old, newTodo] : [newTodo]
    );
    
    // Retourner le contexte pour l'annulation
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    // Annulation en cas d'erreur
    if (context?.previousTodos) {
      queryClient.setQueryData(['todos'], context.previousTodos);
    }
  },
  onSettled: () => {
    // Toujours récupérer après erreur ou succès
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

Requêtes dépendantes

Chaînez les requêtes qui dépendent les unes des autres :

const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: () => getUserByEmail(email),
});

const { data: projects } = useQuery({
  queryKey: ['projects', user?.id],
  queryFn: () => getProjectsByUser(user.id),
  enabled: !!user?.id, // Ne s'exécute que quand l'ID utilisateur existe
});

Requêtes infinies

Gérez les données paginées avec défilement infini :

import { useInfiniteQuery } from '@tanstack/react-query';

const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
} = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam = 0 }) => fetchProjects({ page: pageParam }),
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  initialPageParam: 0,
});

Pièges courants et solutions

Oublier le QueryClientProvider

Si vos requêtes retournent undefined ou ne fonctionnent pas du tout, vérifiez que votre application est enveloppée avec QueryClientProvider. C’est l’erreur de configuration la plus courante.

Utiliser TanStack Query pour l’état local

TanStack Query est pour l’état serveur. Ne l’utilisez pas pour les entrées de formulaire, les bascules d’interface ou autre état uniquement côté client. Restez avec useState ou useReducer pour ceux-ci.

Clés de requête statiques avec données dynamiques

Incluez toujours les paramètres dynamiques dans vos clés de requête :

// ❌ Incorrect - affichera les mêmes données pour tous les utilisateurs
useQuery({
  queryKey: ['user'],
  queryFn: () => fetchUser(userId),
});

// ✅ Correct - entrée de cache unique par utilisateur
useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

Conclusion

TanStack Query transforme la récupération de données React d’un processus manuel et sujet aux erreurs en un système déclaratif et robuste. En traitant l’état serveur comme fondamentalement différent de l’état client, il élimine le code répétitif tout en ajoutant des fonctionnalités puissantes comme la mise en cache, la synchronisation et les mises à jour optimistes.

Le changement mental est simple : arrêtez de penser à comment récupérer les données et commencez à penser à quelles données vous avez besoin. Déclarez vos exigences à travers les clés et fonctions de requête, puis laissez TanStack Query gérer la complexité de maintenir tout synchronisé.

Commencez petit—remplacez une récupération useEffect par useQuery. Une fois que vous verrez les états de chargement instantanés, les nouvelles tentatives automatiques et la mise en cache transparente en action, vous comprendrez pourquoi TanStack Query est devenu essentiel pour les applications React modernes.

FAQ

C'est la même bibliothèque. React Query a été renommé TanStack Query pour refléter qu'il supporte maintenant plusieurs frameworks au-delà de React. Le package spécifique à React est toujours maintenu et utilise la même API que vous connaissez.

Oui, mais vous n'en avez probablement pas besoin pour l'état serveur. TanStack Query gère automatiquement la mise en cache et le partage de données entre composants. Utilisez Redux ou Context pour le véritable état client comme les préférences utilisateur ou l'état d'interface, et TanStack Query pour tout ce qui vient d'une API.

Par défaut, les requêtes récupèrent lors du focus de la fenêtre, de la reconnexion et du montage. Vous contrôlez la fraîcheur avec staleTime (combien de temps les données restent fraîches) et gcTime (combien de temps garder les données inutilisées). Les requêtes obsolètes récupèrent en arrière-plan tout en affichant instantanément les données mises en cache.

L'invalidation marque les requêtes comme obsolètes mais ne récupère pas immédiatement—elle attend que la requête soit utilisée à nouveau. La récupération directe déclenche une requête réseau immédiate. Utilisez l'invalidation après les mutations pour de meilleures performances, car elle ne récupère que les requêtes qui sont réellement affichées.

TanStack Query excelle dans les mises à jour quasi temps réel grâce au polling et aux intervalles de récupération. Pour de vrais besoins temps réel, combinez-le avec des WebSockets : utilisez le socket pour les mises à jour en direct et TanStack Query pour le chargement initial des données et le fallback quand le socket se déconnecte.

Oui. TanStack Query 5+ est entièrement compatible avec React 19. Les APIs utilisées ici (requêtes, mutations, invalidation) sont inchangées, donc les exemples de cet article fonctionneront sans modification.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers