Published on

Exemple avancé de projet React / Redux

Dans cet exemple nous allons aller plus loin avec Redux et utiliser des middlewares pour gérer nos requêtes API en asynchrone et avoir une persistance des données au moment des changements de page ainsi qu’au re-chargement.

Description du projet

Le but est de pouvoir rechercher des musiques via un moteur de recherche en s’appuyant sur l’API gratuite de Deezer et de pouvoir consulter le profil d'un artiste.

Pour accéder au moteur il est impératif d’indiquer son prénom, sans devoir le redemander à chaque rechargement de page. La personne pourra également se déconnecter pour changer de nom d’utilisateur.

Introduction aux middlewares

Dans ce projet nous allons utiliser des middlewares, qui permettent d’étendre les fonctionnalités de Redux, les voici :

  • redux-promise-middleware : Permet de gérer les états d’appels API via le reducer
  • redux-thunk : Permet d’avoir des actions en asynchrone et d’imbriquer des actions
  • react-router-redux : Permet de connecter le router avec Redux
  • redux-persist : Ce n’est pas vraiment un middleware mais redux-persist permet de sauvegarder le state et de réhydrater le store au rechargement de la page, par défaut l’ensemble des reducers reprennent leurs valeurs initiales au chargement

Architecture

Le projet est typique des projets en React et facilement scalable pour répondre aux applications plus importantes avec une équipe de développeurs sur le sujet. En fonction des projets il y a certaines évolutions à apporter surtout quand la partie métier est plus complexe, mais la structure globale reste la même pour Redux, pages et composants.

  • Composants : Il est préférable de faire un dossier par composant pour y inclure le style ainsi que les différents assets qu'il va pouvoir exploiter. Le dossier src/components possède un dossier shared qui inclut l'ensemble des composants dit génériques. Les autres composants plus spécifiques resteront dans le dossier principal.

  • API : Le dossier API permet d'avoir une vue sur l'ensemble des requêtes qu'il est possible de faire à notre webservice, il est impératif de ne pas avoir de requêtes Axios ou Request dans les composants ou les pages, mais de toujours passer par une action, nous verrons par la suite pourquoi c'est important avec les middlewares.

  • Pages : Ce dossier n'est pas nouveau, mais il est important de préciser qu'il s'agit des pages de notre application, dans le cadre de projets plus importants il est possible de retrouver dans ce dossier les uses cases de la partie métier, une page peut en effet avoir plusieurs comportements en fonction de l'utilisateur. Pour découper les pages de façon simple, il suffit de regarder notre router dans src/pages/Main pour en déduire les dossiers. Il est possible ensuite de subdiviser les dossiers pour les uses cases ou les déclinaisons.

Pour le reste des dossiers rien de nouveau, un dossier pour les constantes que nous allons utiliser avec les reducers, ainsi qu'un dossier pour les actions.

Router

Dans ce projet j'ai intégré react-router-dom qui permet d'avoir une dynamisation des pages couplée à history qui va permettre d'avoir un historique de navigation dans l'application. Notre router ce trouve dans src/pages/Main qui définit l'ensemble des pages auxquelles il est possible d'accéder. Dans notre exemple, le router réagit différement en fonction de l'utilisateur: si la personne est connectée, elle aura accès à la page profil d'un artiste, en revanche si cette personne n'a pas renseigné de prénom elle tombera sur la page 404.

Grâce au router, il est possible de servir des pages de façon dynamique sur une même url. Il est possible de le faire dans les pages également mais cette solution est bien plus propre et permet d'éviter les problèmes d'hydratation de nos props. C'est le cas de la page principale / qui une fois la personne connectée va chercher la page Search , ou dans le cas contraire la page de login (User ).

Il est également possible de passer les variables dans les routes et d'avoir des accès du type user/:id_user qu'il est possible d'exploiter dans les composants par la suite.

Redux Promise Middleware

Ce premier middleware est extrêmement pratique, car en fonction de l'état de votre requête il va hydrater différemment votre reducer et faire appel à différents types. Voilà pourquoi il est important de passer par des actions / reducers pour les requêtes API.

Prenons l'exemple de la requête qui va faire la recherche de musique, dans le fichier src/actions/search.action.js la fonction search retourne comme payload une promise, c'est à dire une fonction asynchrone. La fonction searchByQuery(query) est une de nos requêtes API, facile de constater grâce à l'import en début de fichier "import { searchByQuery } from "../api/search.api"; .

Quand nous allons faire appel dans notre code à la fonction search() , RPM va appeler automatiquement un type dynamique GET_SUGGESTIONS_PENDING ce qui implique que la requête est en attente, elle peut également appeler GET_SUGGESTIONS_FULFILLED quand la requête est terminée avec un retour 200 ou GET_SUGGESTIONS_REJECTED quand le serveur ne répond pas ou retourne une erreur.

Pour jouer avec ces différents types il suffit de déclarer une constante dans le fichier approprié et l'appel se fera automatiquement dans votre reducer comme dans cet exemple :

const search = (state = initState, action) => {
  switch (action.type) {
    case searchConst.SET_QUERY:
      return { ...state, query: action.payload }
    case searchConst.GET_SUGGESTIONS_FULFILLED:
      return { ...state, suggestions: action.payload.data.data }
    case userConst.LOGOUT:
      return initState
    default:
      return state
  }
}

Ici une fois que la requête search de type GET_SUGGESTIONS est retournée, je stocke le contenu de la réponse dans mon state. Il est possible avec ceci de déclencher des loaders par exemple en les activant au moment du pending et de les retirer au moment de fulfilled.