liksi logo

Comment migrer un composant React/Redux vieillissant vers React-Query ?

Yoann Delion - Publié le 04/07/2023
Comment migrer un composant React/Redux vieillissant vers React-Query ?

Dan Abramov, le créateur de Redux l’a dit lui-même : “You Might Not Need Redux” (vous n’avez peut-être pas besoin de Redux). Mais dans ce cas, on peut être tenté d’utiliser d’autres librairies tierces pour nous aider dans le développement de notre application, tout particulièrement pour gérer notre cache ou nos différents appels API. Je vais vous présenter dans cet article React-Query, un gestionnaire de données nous donnant un contrôle simple et puissant sur la récupération et la mise en cache de nos données serveur, et une gestion simplifiée des erreurs.

Tout d’abord, rappelons ce qu’est Redux

Redux est une librairie javascript de gestion d’état d’une application. Souvent utilisée avec React, son but principal est de définir un store global contenant l’état entier de l’application et des actions associées définissant les possibilités de modification de celui-ci.

Redux a des avantages : grâce aux règles que l’on doit définir pour modifier le store, il est prévisible. Cette prévisibilité permet également de faciliter l’écriture de tests de l’application. Redux trace et garde un suivi de tout l’état de l’application, ce qui peut permettre de rendre le débogage plus simple et efficace. Enfin, la librairie a été éprouvée, et sa communauté conséquente permet de trouver facilement du support au besoin.

Cependant, Redux est un outil très verbeux et qui demande beaucoup beaucoup de boilerplate pour le configurer et le faire fonctionner. Redux est également difficile à appréhender, surtout pour ses nouveaux développeurs.

Redux a été pendant longtemps le go-to pour un projet React, ce qui fait qu’il a souvent été utilisé à tort pour des petits projets. Redux peut également vite augmenter la taille du bundle de l’app, vu qu’il rajoute des dépendances supplémentaires au projet.

Voici un schéma provenant de la documentation de Redux (https://redux.js.org/) tentant d’expliquer brièvement son fonctionnement :

Pourquoi passer à React-Query ?

Ainsi, la question que vous pouvez légitimement vous poser après cette belle présentation de Redux est : pourquoi passer à React-Query ? Hormis procéder au changement suite à la demande du client, React-Query à quelques avantages sur Redux :

  • Premier avantage et pas des moindres, React-Query simplifie le code en utilisant des hooks pour gérer ses queries et mutations. Il évite ainsi la quantité de boilerplate nécessaire à Redux. Ce qui peut rendre l’application plus maintenable, et le code plus lisible (à condition de bien organiser ces hooks).
  • Le système de cache puissant de React-Query permet de récupérer seulement les données nécessaires, réduisant ainsi le nombre d’appels API effectués. Ce qui peut impacter sur les performances de l’application.
  • React-Query gère lui-même ses données et les met à jour au besoin (après une mutation par exemple).
  • Il offre également une meilleure gestion des erreurs, des succès et des temps de chargement.

En bref, React-Query rend le code plus lisible, plus performant, et plus pratique pour la gestion en temps réel, tout en offrant une meilleure expérience de développement.

Comment passer à React-Query ?

Maintenant l’étape fatidique, comment passer à React-Query depuis un code basé sur Redux ?

Voici les étapes principales pour effectuer cette migration

  • Commencer par l’installation de React-Query avec npm install react-query ou yarn add react-query
  • Pour le bon fonctionnement de la librairie et que ces hooks soient accessibles, il faut englober tout ou partie de l’application par un composant QueryClientProvider, de la même manière que le Provider de Redux. Ce provider prendra en props un client créé comme ceci : const queryClient = new QueryClient()
  • Ensuite, il faut retirer Redux et le code qui lui est propre : les actions, les reducers et les containers ou les HOC (High Order Component) qui utilisent connect
  • Remplacez les actions avec le hook useQuery. Ce dernier permet de récupérer et de gérer les données d’une requête spécifique. Il peut également servir à remplacer dispatch qui servait à mettre à jour le store.
  • Les HOC et containers connect n’ont plus d’utilité dans une codebase React-Query, étant donné que les queries et les mutations seront faites directement par des hooks dans le corps d’un composant. On peut donc supprimer ceux-ci, en pensant à modifier les endroits où les imports de ces composants connects et HOC sont faits.
  • Les reducers seront remplacés par l’usage de useMutation. Ce hook sert à mettre à jour les données d’une query spécifique. Il remplace également les appels à dispatch pour mettre à jour le store.
  • Bien vérifier et tester l’application pour être sûr de n’avoir perdu aucune fonctionnalité et s’assurer que celle-ci a toujours le comportement souhaité.

Pour être plus parlant, voici un exemple d’un composant React faisant un appel API et affichant les données reçues en utilisant Redux :

import React from 'react';
import { connect } from 'react-redux';
import { fetchData } from './actions';

class MyComponent extends React.Component {
  componentDidMount() {
    this.props.fetchData();
  }

  render() {
    const { data, isLoading, error } = this.props;

    if (isLoading) {
      return <p>Loading...</p>;
    }

    if (error) {
      return <p>Error: {error.message}</p>;
    }

    return (
      <div>
        {data.map(item => (
          <p key={item.id}>{item.name}</p>
        ))}
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    data: state.data,
    isLoading: state.isLoading,
    error: state.error
  };
};

const mapDispatchToProps = dispatch => {
  return {
    fetchData: () => dispatch(fetchData())
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

Le même exemple en utilisant React-Query :

import React from 'react';
import { useQuery } from 'react-query';
import { fetchData } from './api';

function MyComponent() {
  const { data, status, error } = useQuery('data', fetchData);

  if (status === 'loading') {
    return <p>Loading...</p>;
  }

  if (status === 'error') {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div>
      {data.map(item => (
        <p key={item.id}>{item.name}</p>
      ))}
    </div>
  );
}

export default MyComponent;

Comme vu précédemment, le composant refactorisé avec React-Query utilise le hook useQuery pour faire l’appel API. Ce hook prend en paramètres une clé et une fonction qui retourne une promesse, ici fetchData. Le hook retourne ensuite un objet que l’on déstructure, contenant les données, le statut de la requête, les propriétés d’erreur…

On enlève le HOC connect et les fonctions mapStateToProps et mapDispatchToProps qui n’ont plus d’utilité dans notre nouveau code, et dans le cas des class components, on remplace la méthode de cycle de vie componentDidMount par le hook useEffect de React.

On peut donc observer que la version utilisant React-Query utilise moins de boilerplate et devient plus lisible. On supprime aussi le besoin de globaliser le store et les actions, nous permettant d’avoir un code plus simple à écrire et donc avec moins de risques d’erreurs.

À noter : Il existe aussi des hooks Redux useDispatch et useSelector permettant de simplifier la partie mapStateToProps et mapDispatchToProps, et éviter d’utiliser connect. Le code du composant devient plus simple, mais la complexité de configuration du store n’est pas diminuée pour autant :

import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchData } from './actions';

function MyComponent() {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchData());
  }, [dispatch]);

  const { data, isLoading, error } = useSelector(state => state);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div>
      {data.map(item => (
        <p key={item.id}>{item.name}</p>
      ))}
    </div>
  );
}

export default MyComponent;

Pour conclure

Voilà, nous avons vu sans rentrer dans le détail comment passer en quelques étapes d’un code Redux vieillissant à React-Query. Bien que cette migration puisse paraître compliquée, on a pu observer que ce n’était pas forcément le cas, mais elle peut tout de même prendre du temps.

Avant de se lancer dans cette migration, il faut savoir si celle-ci est nécessaire pour notre application et si React-Query répond à nos besoins. React-Query sert à gérer des données de manière asynchrone, il n’a pas pour but de gérer un store global. Pour gérer un store global sans Redux il existe d’autres solutions utilisables en parallèles de React-Query (API context notamment).

Comme tout choix technique il est important de bien définir les besoins de l’application, car même si Dan Abramov dit que “You Might Not Need Redux”, il se peut que ce soit la solution la plus adaptée pour votre projet quand même ! De plus, aujourd’hui il existe maintenant Redux-Toolkit (https://redux-toolkit.js.org/) qui nous permet d’utiliser Redux de manière beaucoup plus simple, supprimant beaucoup de complexité et de boilerplate, un des principaux défauts de Redux.

Mais il faut garder à l’esprit que ce dernier n’aura pas toujours le même intérêt que React-Query, alors pour conclure, si vous voulez gérer les requêtes et les mises à jour de données de manière fluide, préférez React-Query, et si vous avez besoin d’un meilleur contrôle sur l’état global de votre application, aller jeter un oeil du côté de Redux-Toolkit.

Derniers articles