• 15 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 01/12/2023

Utilisez combineReducers pour faciliter l’ajout de nouvelles fonctionnalités

Il est temps de quitter le confort de notre petit Tennis Score pour s’attaquer à un projet plus conséquent : Shiny !

Shiny est une agence de freelances (fictive) qui a déjà un superbe site web. Ce site permet aux potentiels clients de découvrir les compétences dont ils ont besoin grâce à un quiz interactif, et de lister les profils des freelances. Et, cerise sur le gâteau, Shiny utilise une API qui contient les différentes données ! C’est donc le terrain de jeu parfait pour s'entraîner à utiliser Redux dans un contexte similaire à celui que vous retrouverez en entreprise !

Exact ! Mais pas de panique, vous pouvez suivre ce cours même si vous n'avez pas suivi le cours Créez une application React complète (ou que vous avez un peu oublié depuis 😇).

L’agence Shiny veut améliorer son site en ajoutant Redux, et c’est vous qui allez vous en charger !

Dans cette partie, nous allons voir ensemble les outils que proposent Redux et React-Redux pour gérer des projets complexes comme Shiny.

Avec Redux on a un seul state et un seul reducer pour toute notre application. C’est un avantage car cela permet de faciliter l’organisation et l’accessibilité de notre state.

En revanche, cela pose un problème : plus notre application grandit et plus le reducer va devenir long et complexe. Heureusement, Redux a pensé à tout et propose une solution :combineReducers !

combineReducers  va nous permettre d’organiser notre state en attribuant chaque partie du state à un sous-reducer différent.

Les éléments users, posts et darkMode de notre state obtiennent leurs propres reducers usersReducer, postsReducer et darkModeReducer.
Chaque partie du state est organisée en sous-reducers différents

Combinez des reducers manuellement

Afin de bien comprendre ce que fait combineReducer  ,  nous allons essayer de reproduire son fonctionnement.

Notre objectif est de manipuler un boolean darkMode  et  une liste de tags  .

Voici notre state initial :

const initialState = {
    darkMode: false,
    tags: ["react", "redux"]
};

Au lieu de créer un reducer pour tout notre state, nous allons créer deux reducers : un pour chacune des propriétés du state.

// reducer pour la propriété darkMode
const darkModeReducer = (state, action) => {
    if (action.type === "toggleDarkMode") {
        return !state;
    }
    if (action.type === "resetApp") {
        return false;
    }
    return state;
};
 
// reducer pour la propriété tags
const tagsReducer = (state, action) => {
    if (action.type === "addTag") {
        return [...state, action.payload];
    }
    if (action.type === "resetApp") {
        return ["react", "redux"];
    }
    return state;
};

Ici, il est important de noter que chaque reducer manipule non pas le state global mais uniquement la partie du state qui l'intéresse. Par exemple, le darkModeReducer  manipule une valeur boolean.

Vous avez peut-être également vu que les deux reducers écoutent la même action resetApp  . En effet, nous verrons que, même avec combineReducer  , les actions restent “globales”, c’est-à-dire que tous les reducers vont recevoir toutes les actions que l’on dispatch  .

Voyons maintenant comment on peut faire fonctionner ces reducers ensemble :

// le reducer est une fonction qui reçoit
// le state et une action
const reducer = (state, action) => {
    return {
        // pour chaque propriété du state, on appelle le reducer correspondant
        // avec la partie du state qui l'intéresse
        darkMode: darkModeReducer(state.darkMode, action),
        tags: tagsReducer(state.tags, action)
    };
};

Pour combiner les reducers, on crée un objet et, pour chaque propriété du state, on appelle le reducer correspondant avec la partie du state qui l'intéresse.

Et voilà, on peut maintenant utiliser le reducer  pour créer le store Redux.

Ce n’est pas un problème de créer un nouvel objet à chaque fois dans le reducer ?

Si, en effet ! Si aucun state ne change, notre reducer retourne quand même un state différent, ce qui peut causer des rendus de composants inutiles 😕.

Mais la fonction combineReducer  de Redux corrige ce problème. C’est pourquoi il est recommandé de l’utiliser !

Utilisez combineReducer

Maintenant que vous savez comment combiner les reducers manuellement, utiliser combineReducer  devrait être un jeu d’enfant.

On commence par importer combineReducer  depuis Redux :

import { combineReducers } from "redux";

On va ensuite appeler cette fonction avec un objet en paramètre. Cet objet va permettre d’indiquer, pour chaque propriété du state, quel reducer utiliser :

const reducer = combineReducers({
    // la propriété darkMode
    // est gérée par le darkModeReducer
    darkMode: darkModeReducer,
    tags: tagsReducer
});

Si on essaie d'exécuter le code ci-dessus, on obtient une erreur :

Une capture d’écran de l’erreur traduite en dessous.
Il nous manque un petit truc !

Voici une traduction de cette erreur : “Le reducer du slice “darkMode” a retourné undefined lors de l’initialisation. Si le state passé au reducer est undefined, vous devez explicitement retourner le state initial. Le state initial ne peut pas être undefined. Si vous souhaitez donner une valeur pour ce reducer, vous pouvez utiliser null à la place.”

Le problème, ici, vient du fait que combineReducer  n’utilise pas le initialState  . À la place, il exécute les différents reducers avec undefined  comme state, et utilise la valeur de retour comme state initial.

Il va donc falloir ajouter le code suivant dans nos reducers :

const darkModeReducer = (state, action) => {
    if (state === undefined) {
        // state initial
        return false;
    }
    // ...
};

On peut également utiliser la syntaxe de valeur par défaut pour obtenir le même résultat puisqu’en cas d’action invalide, le state est retourné :

const darkModeReducer = (state = false, action) => {
    // ...
    return state;
};

L'avantage de cette technique, c’est qu’elle permet de définir un state initial pour chaque reducer au lieu d’un seul énorme state pour toute notre application !

On va donc pouvoir appeler createStore  sans passer le state initial en paramètre :

const store = createStore(reducer);

Et voilà ! Vous savez désormais comment combiner plusieurs reducers afin de mieux organiser votre code Redux.

 Nous allons voir à présent comment insérer la fonction  combineReducer  :

Maintenant que l’on a plusieurs reducers, on va avoir besoin d’un peu d’organisation ! Suivez-moi dans la suite du cours pour découvrir comment on organise les fichiers d’une application React-Redux.

Organisez vos fichiers Redux

Vous vous y attendiez peut-être un peu, mais il y a plusieurs manières d’organiser le code d’une application React-Redux.

Nous allons donc passer en revue les méthodes les plus utilisées pour analyser leurs avantages et inconvénients.

Séparez les containers et les views

Le système de views/containers consiste à connecter à Redux seulement un nombre limité de composants : les containers. Les autres composants sont alors appelés des views.

On organise alors les fichiers dans deux dossiers views  et containers  .

Ce système, aujourd’hui peu utilisé, est plutôt déconseillé car il n’offre pas de réel intérêt. Comme précisé dans le chapitre 3 de la partie 2, il est recommandé de connecter le plus de composants possibles avec useSelector  .

Organisez vos fichiers par type

Cette technique consiste à créer un dossier reducers  et un dossier actions  pour ranger les fichiers. On trouve encore ce genre d’organisation en entreprise, mais je vous la déconseille pour de nouveaux projets. Il est en effet parfois difficile de s’y retrouver, car la logique d’une fonctionnalité se retrouve séparée dans deux fichiers qui ne sont pas côte à côte...

Voici un exemple d’organisation par type de fichiers :

src
 reducers
 darkMode.js
 users.js
 posts.js
actions
 darkMode.js
 users.js
 posts.js

Utilisez des constantes pour identifier vos actions

Au lieu d’écrire le type  de l’action directement dans l’action creator et dans le reducer, on utilise une constante qui contient le nom.

Cette méthode est très courante et plutôt pratique, puisqu’elle permet d’éviter les fautes de frappe sur les noms des actions.

// actions.js
export const PLAY_PAUSE = "playPause";
export const RESTART = "restart";
export const POINT_SCORED = "pointScored";
 
// actions creators
export const playPause = () => ({ type: PLAY_PAUSE });
 
// reducer
function reducer(state, action) {
    if (action.type === RESTART) {
        // ...
    }
    // ...
}

Organisez vos fichiers par fonctionnalité

L’approche actuellement recommandée, y compris par la documentation de Redux, est d’organiser vos fichiers par fonctionnalité.

On va donc créer un dossier par fonctionnalité et y mettre tout le code Redux associé (actions et reducers). On peut même étendre ce genre d’organisation pour mettre également les composants React dans ce même dossier.

src
 features
  darkMode
   reducer.js
   actions.js
   DarkModeButton.jsx
  users
  posts

Utilisez un suffixe pour identifier le contenu d’un fichier

Afin de facilement identifier les fichiers dans nos dossiers de fonctionnalité, on peut utiliser un suffixe dans le nom des fichiers.

src
 features
  darkMode.reducer.js
  darkMode.actions.js

Cela permet de facilement identifier le type de fichier tout en évitant d’avoir des dizaines de fichiers nommés reducer.js  ou actions.js  .

Mettez actions et reducers dans un seul fichier

Tout mettre dans un seul et même fichier peut sembler contre-productif, mais en réalité on est souvent amené à modifier les actions et le reducer en même temps.

Tout avoir dans le même fichier permet d’éviter de devoir constamment naviguer d’un fichier à l’autre, et fait donc gagner du temps.

De plus, si jamais le fichier devient trop gros il est toujours possible de le diviser, soit en plusieurs fonctionnalités, soit en utilisant combineReducer  .

Dans le cas de Shiny, nous allons utiliser cette technique en combinaison avec un dossier features  pour le code Redux. Les composants React étant déjà faits, il n’est pas vraiment judicieux de tout réorganiser.

On va donc utiliser une structure de fichier qui ressemble à ça :

src
 features
  theme.js (actions et reducers du thème)
  freelances.js (actions et reducers de la liste de freelances)
 utils
  store.js (création du store)
  selectors.js (selectors de l’application)

Initialisez Shiny

Pour la suite de ce cours, nous allons travailler sur Shiny, une application React qui interagit avec une API.

Vous allez donc avoir besoin de deux choses :

  • Lancer l’API de Shiny sur votre ordinateur.

  • Récupérer le code de l’application Shiny. Ce sera votre base de travail pour les exercices.

Récupérez et lancez l’API de Shiny

Pour lancer l’API de Shiny sur votre ordinateur, vous allez devoir récupérer le code depuis le repository GitHub de l’API de Shiny avec la commande  git clone  :

git clone 
git@github.com:OpenClassrooms-Student-Center/7150626-React-Redux-Shiny-API.
git

Une fois le clonage effectué, il faudra vous rendre dans le dossier du projet avec le terminal (  cd  7150626-React-Redux-Shiny-API  ), installer les dépendances du projet avec la commande yarn  puis démarrer l'application avec la commande suivante :

yarn start

Vérifiez que le serveur fonctionne correctement en visitanthttp://localhost:8000/  . Vous devez voir le message “API - React intermédiaire” s’afficher.

Message d’accueil de l’API Shiny
Message d’accueil de l’API Shiny

Récupérez le code de base de l’application Shiny React

Pour récupérer le code de base de l’application Shiny, vous allez devoir télécharger le code base sur notre repository Github.

Une fois le fichier téléchargé, vous allez devoir extraire le fichier pour obtenir les différents fichiers du projet.

Ouvrez ensuite le dossier du projet dans un terminal (  cd 7150626-React-Redux-Shiny-P3C1S4-base   ), puis lancez la commande yarn  pour installer les dépendances.

Enfin, exécutez la commande  yarn start   pour lancer l'application Create-React-App en mode développement.

Vous devriez voir Shiny s’ouvrir surhttp://localhost:3000/, vous êtes maintenant prêt à ajouter Redux et React-Redux !

Page d’accueil de Shiny
Page d’accueil de Shiny

Ça ne fonctionne pas ? Vous n'êtes pas sûr de la démarche à suivre ? Visionnez le screencast ci-dessous pour voir l’installation de Shiny ainsi qu’un petit tour du propriétaire !

Exercez-vous

Prêt à ajouter Redux à Shiny pour lui donner des super-pouvoirs ? 💪🏻

Commencez par vérifier que Shiny fonctionne correctement. Une fois Shiny en place, vous allez pouvoir ajouter Redux et React-Redux afin de mettre en place la logique du thème dans Redux.

Voici la structure de fichier attendu :

├── README.md
├── package.json
├── public
│   ├── ...
├── src
│   ├── index.jsx
│   ├── assets
│   │   └── ...
│   ├── components
│   │   └── ...
│   ├── features
│   │   └── theme.js
│   ├── pages
│   │   └── ...
│   └── utils
│       ├── context
│       ├── hooks
│       ├── style
│       ├── test
│       ├── selectors.js
│       └── store.js
└── yarn.lock

Une fois que vous avez terminé, allez jeter un œil à la version corrigée sur la branche P3C1S5-solution du repository React-Redux-Shiny à cette adresse.

En résumé

  • La fonction combineReducer  permet de séparer le reducer Redux en plusieurs petits reducers.

  • Il est conseillé d’organiser les projets Redux par fonctionnalité, avec les actions et le reducer d’une fonctionnalité dans un seul fichier.

Avec tous ces fichiers, ça risque de devenir un peu compliqué de suivre ce qu’il se passe dans Redux, non ?

C’est vrai mais pour ne pas s'y perdre, Redux possède une botte secrète : les Redux Devtools !

Exemple de certificat de réussite
Exemple de certificat de réussite