Comment React Query a réduit de 500 lignes la couche API d’une application React Native
Une étude de cas réelle sur React Native montrant comment le passage d’un chargement de données manuel via useEffect à React Query a permis d’éliminer le code redondant et d’améliorer le cache ainsi que la gestion hors ligne.
Introduction
Il y a quelque temps, une base de code React Native que vous pourriez reconnaître a rencontré un problème familier.
Presque chaque écran nécessitant des données à distance suivait le même schéma :
- Des appels de récupération de données insérés à l’intérieur de
useEffect - Plusieurs indicateurs de chargement distincts
- Des blocs personnalisés pour gérer les erreurs
- Une logique de mise à jour par tiraillement développée manuellement
- Des mécanismes de tentative automatique
- Des solutions temporaires pour le stockage en mémoire locale
D’un point de vue fonctionnel, rien de tout cela n’était défectueux. Mais maintenir cette cohérence sur des dizaines d’écrans est devenu une véritable charge en termes de maintenance.
Le passage à React Query (de TanStack) a permis à l’équipe d’éliminer une grande quantité de code réseau répétitif, tout en obtenant un meilleur mécanisme de mise en cache, une gestion plus propre du chargement et un fonctionnement hors ligne plus fiable. Cette bibliothèque intègre un mécanisme de mise en cache des requêtes, un rechargement automatique en arrière-plan et une logique sensible au réseau, ce qui réduit fortement le besoin d’écrire soi-même cette mécanique de gestion d’état.
Ce qui suit présente une comparaison côte à côte de l’ancienne approche manuelle par rapport à la version React Query, en s’appuyant sur des exemples tirés de vraies interfaces React Native en production.
Les problèmes liés à la gestion traditionnelle des API
La configuration d’une interface typique ressemblait à peu près à ceci :
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const response = await api.getPosts();
setData(response);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
Cette même structure de base réapparaissait sans cesse dans toute l’application.
Chaque interface devait gérer de manière indépendante :
- Un indicateur de chargement
- Un indicateur d’erreur
- Gestion des tentatives de réessai
Au fur et à mesure que l’application grandissait, il devenait de plus en plus difficile de maintenir la cohérence de ce schéma répétitif.
Apparition de React Query
Au même écran, réécrit avec React Query, on aboutit à ceci :
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
C’est toute l’implémentation.
React Query gère automatiquement tout ce qui suit :
- Indicateurs de chargement
- États d’erreur
- Déduplication des requêtes en cours identiques
- Récupération des données en arrière-plan
- Gestion de la cache
- Réessai des requêtes échouées
- Réaction à la réconnexion au réseau
La plupart de ces fonctionnalités sont disponibles gratuitement dès le départ, et il est possible de les ajuster via des options telles que staleTime, gcTime, la configuration des tentatives de réessai et les paramètres de récupération des données.
Comparaison n°1 : Demandes réseau
Au préalable de React Query
Imaginez trois écrans distincts qui ont tous besoin des mêmes données de profil utilisateur.
Sans aucun niveau de mise en cache partagée, chacun envoie sa propre demande :
Profile Screen → API Call
Settings Screen → API Call
Dashboard Screen → API Call
Résultat : trois appels réseau séparés pour les mêmes données.
Avec React Query
Profile Screen → API Call
Settings Screen → Cached Data
Dashboard Screen → Cached Data
Résultat cette fois : un seul appel réseau.
React Query stocke les résultats sous une clé de requête et partage ces données mises en cache avec tous les composants qui les demandent. Tout écran qui demande ensuite la même clé obtient immédiatement la valeur en cache, tandis qu’un rechargement en arrière-plan peut la mettre à jour silencieusement.
Résultat en production
Sur les écrans que les utilisateurs consultent fréquemment, cela se traduit par :
- Bien moins de demandes dupliquées vers l’API
- Moins de charge sur les serveurs backend
- Transitions plus rapides entre écrans
Comparaison n°2 : Efficacité du cache
On peut dire que c’est là que React Query apporte la plus grande valeur ajoutée.
Lorsque la même requête est de nouveau sollicitée avant que sa copie en cache ne devienne obsolète :
useQuery({
queryKey: ["products"],
queryFn: getProducts,
staleTime: 300000,
});
L’interface peut afficher immédiatement les données en cache, avec React Query qui les met à jour discrètement en arrière-plan si nécessaire. Les entrées en cache sont conservées et finissent par être collectées selon les paramètres que vous définissez.
Exemple concret
Prenons l’écran de la liste des produits d’une application e-commerce.
Sans cache, l’ouverture de cet écran signifie :
Open Products
↓
Network Request
↓
Navigate Back
↓
Open Products Again
↓
Network Request
Avec React Query, le même processus devient :
Open Products
↓
Network Request
↓
Navigate Back
↓
Open Products Again
↓
Instant Cached Data
La différence est que l’application semble nettement plus réactive pour l’utilisateur.
Comparaison n°3 : États de chargement
Au préalable à l’adoption de React Query, suivre les états de chargement impliquait de gérer plusieurs variables booléennes :
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [isRetrying, setIsRetrying] = useState(false);
Par la suite, une seule appel à l’hook expose tout ce qui est nécessaire :
const {
isLoading,
isFetching,
isRefetching,
} = useQuery(...)
React Query fait la distinction entre le premier chargement et tous les fetch en arrière-plan ultérieurs, ce qui rend la logique de l’interface beaucoup plus simple à comprendre.
Bénéfice réel
Au lieu d’afficher un indicateur de chargement en plein écran à chaque fetch, l’application peut faire la différence entre :
- Chargement initial : indicateur de chargement en plein écran
- Mise à jour en arrière-plan : un indicateur de chargement petit et discret
- Données déjà mémorisées : aucune interruption visible du tout
Cela donne l’impression à l’utilisateur que l’application est bien plus réactive.
Comparaison n°4 : Support hors ligne
Le comportement hors ligne est l’un de ces aspects que les équipes ont tendance à sous-estimer.
Gérer cela manuellement se fait généralement comme suit :
Check Connectivity
Pause Requests
Retry Later
Handle Errors
Refetch On Reconnect
Cela signifie que de la logique de connexion personnalisée est dispersée dans l’ensemble du code.
React Query propose plutôt une gestion intégrée en ligne et hors ligne, ainsi qu’une fonction de rechargement qui réagit aux événements de réconnexion. Dans React Native, vous pouvez l’intégrer en utilisant onlineManager conjointement avec les écouteurs d’état réseau de la plateforme.
Par exemple :
onlineManager.setEventListener(...)
React Query peut également être configuré pour fonctionner en mode priorité hors ligne, ajustant ainsi son comportement réseau en conséquence.
Exemple concret
Prenons une application de lecture de nouvelles comme exemple.
Au cours d’une journée, la connexion de l’utilisateur peut évoluer comme suit :
- Matin : en ligne
- Après-midi : hors ligne
- Soir : de nouveau en ligne
Pendant toute cette séquence, les articles précédemment stockés en cache restent lus, et dès que la connexion est rétablie, de nouveaux contenus peuvent être synchronisés automatiquement.
Cas d’usage réels
1. Applications de nouvelles
Cela apporte :
- Articles en cache
- Mise à jour automatique en arrière-plan
- Baisse du trafic API global
- Possibilité de continuer à lire hors ligne
2. Applications e-commerce
Avantages :
- Listes de produits en cache
- Préchargement anticipé des catégories
- Navigation plus rapide entre les sections
- Expérience d’achat nettement plus fluide
React Query permet également de précharger les données avant même que la navigation ne commence, réduisant ainsi les temps d’attente perçus.
3. Applications de tableau de bord
Avantages :
- Widgets qui se mettent à jour automatiquement
- Cache partagé entre plusieurs écrans
- Réduction des communications réseau
- Gestion de l’état beaucoup plus simple dans l’ensemble
Cette approche convient particulièrement bien aux tableaux de bord analytiques et aux panneaux d’administration.
Ce que nous avons effectivement supprimé
Lorsque la migration a été terminée :
Supprimé
- Gestion personnalisée de l’état de chargement
- Codage manuel pour les tentatives répétées
- Appels API sortants redondants
- Code de base pour le rafraîchissement par tirage
- Logique de cache développée en interne
- Gestion manuelle du rechargement
Ajouté
- La bibliothèque React Query elle-même
- Clés de requête
- Une instance
QueryClient
L’effet global : environ 500 lignes de code de gestion des API n’étaient plus nécessaires.
Lorsque React Query peut ne pas être nécessaire
Recourir à React Query pourrait être excessif si :
- Votre application effectue seulement quelques appels API
- Les données de base changent à peine
- Le stockage en cache n’est vraiment pas nécessaire
- Le comportement hors ligne n’a pas d’importance pour votre cas d’usage
Cependant, pour la plupart des applications de production, les avantages finissent par l’emporter sur la courbe d’apprentissage initiale assez rapidement.
Conclusions finales
React Query n’est pas simplement une autre façon de récupérer des données.
Ce outil constitue une solution complète de gestion de l’état serveur, en éliminant le code API répétitif tout en améliorant le cache, le comportement de chargement, l’efficacité réseau et l’expérience hors ligne.
Le plus grand avantage n’était pas la performance brute.
C’était plutôt la réduction de la complexité.
C’est cette combinaison qui a rendu la migration intéressante.