• 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

Modifiez le fonctionnement de Redux grâce aux middlewares

Découvrez les middlewares Redux

Les middlewares Redux permettent de changer le fonctionnement du store. Cela permet par exemple d’ajouter un système de logging, ou bien de faciliter la manipulation d’action asynchrone.

Un middleware Redux est une fonction qui ressemble à ça :

const myMiddleware = store => next => action => {
    // Ici on peut manipuler l'action pour changer
    // le comportement de Redux !
    return next(action)
}

Qu’est-ce que c’est que toutes ces flèches (  =>  ) ?

Les flèches sont en fait des fonctions. Seulement, ici on a une fonction qui retourne une fonction qui retourne une fonction ! On aurait également pu écrire le code de cette manière :

function myMiddleware(store) {
    return function (next) {
        return function (action) {
            // Ici on peut manipuler l'action pour changer
            // le comportement de Redux !
            return next(action);
        };
    };
}

Cette syntaxe un peu étrange est liée au fonctionnement des middlewares. Si vous voulez comprendre pourquoi on utilise toutes ces fonctions (et que vous comprenez l’anglais), je vous invite à lire la page dédiée au middleware dans la documentation Redux ici.

Ce qui va nous intéresser ici, ce sont les variables next  et action  :

  • action  correspond à une action envoyée au store par dispatch  .

  • next  est une fonction qui va envoyer l’action au prochain middleware ; si le middleware est le dernier, next  va envoyer l’action au reducer pour mettre à jour le state.

On va donc pouvoir utiliser ces deux variables pour intercepter l’action et modifier le comportement de Redux.

Voici un premier exemple plutôt simple d’un middleware qui affiche chaque action dans la console :

const logMiddleware = store => next => action => {
    // on affiche chaque action dans la console
    console.log(action);
    return next(action)
}

Et voilà un autre middleware qui ignore certaines actions et affiche un warning dans la console :

const deprecatedActionsMiddleware = store => next => action => {
    if (action.type = "menu/hide") {
        console.warn(`Laction "menu/hide" est dépréciée, veuillez utiliser l'action "menu/compactMode" à la place`);
        // on n'exécute pas next pour ne pas envoyer l'action au reducer !
        return;
    }
    // sinon on envoie l'action au reducer
    return next(action)
}

Maintenant que nos middlewares sont créés, il va falloir les intégrer à la configuration du store  . Pour cela, on peut utiliser la fonction configureStore  de Redux-Toolkit qui accepte un paramètre middleware  :

configureStore({
 reducer: { /* ... */ },
 middleware: [
   logMiddleware,
   deprecatedActionsMiddleware
 ]
})

D'ailleurs, Redux-Toolkit utilise trois middlewares bien pratiques par défaut :

  • Redux-immutable-state-invariant (actif uniquement en développement) pour vérifier que l’on ne fait pas de mutations accidentelles dans le reducer.

  • Serializability Middleware (actif uniquement en développement) pour vérifier que le state et les actions ne contiennent pas des données non sérialisables qui peuvent poser des problèmes avec les Devtools, par exemple.

  • Redux-Thunk qui permet de faciliter la manipulation d’action asynchrone. C’est le middleware Redux le plus utilisé, c’est pourquoi la section suivante lui est dédiée.

Attention, cependant, car lorsque l’on passe un tableau à l’option middleware  de configureStore  , ce dernier remplace les middlewares par défaut. On peut facilement résoudre ce problème en utilisant la fonction getDefaultMiddleware  de Redux-Toolkit :

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
 
export default configureStore({
    reducer: { /* ... */ },
    middleware: [
        ...getDefaultMiddleware(),
        logMiddleware,
        deprecatedActionsMiddleware
    ]
})

Utilisez le thunk middleware

Redux-Thunk est le middleware le plus populaire pour Redux. Il permet de simplifier la manipulation des actions asynchrones.

Pour rappel, jusqu’à maintenant, nous avons passé le store en paramètre des actions asynchrones pour accéder à getState  et dispatch  . Voici, par exemple, l’action fetchOrUpdateFreelances  :

async function fetchOrUpdateFreelances(store) {
    // ...
}

Pour accéder au store, on a ensuite utilisé le hook useStore  dans le composant.

Avec le thunk middleware, au lieu de passer le store directement, on va pouvoir donner notre fonction directement à Redux (par défaut, Redux accepte uniquement des objets actions). Le thunk middleware va alors appeler cette fonction avec dispatch  et getState  en paramètre.

On peut comprendre le fonctionnement de Redux-Thunk en lisant le code source qui ne fait que quelques lignes :

const thunkMiddleware = (store) => (next) => (action) => {
    // si l'action est une fonction...
    if (typeof action === 'function') {
        // on l'exécute avec `dispatch` et `getState` en paramètre
        return action(store.dispatch, store.getState)
    }
    // sinon on envoie l'action au reducer
    return next(action)
}

Une fois Redux-Thunk installé (c’est le cas par défaut avec Redux-Toolkit), on peut donc envoyer la fonction fetchOrUpdateFreelances  directement avec dispatch  . Attention toutefois car il faut changer un peu la fonction pour qu’elle accepte dispatch  et getState  au lieu du store  en paramètre.

export async function fetchOrUpdateFreelances(dispatch, getState) {
    const status = selectFreelances(getState()).status
    if (status === 'pending' || status === 'updating') {
        return
    }
    dispatch(freelancesFetching())
    try {
        const response = await fetch('http://localhost:8000/freelances')
        const data = await response.json()
        dispatch(freelancesResolved(data))
    } catch (error) {
        dispatch(freelancesRejected(error))
    }
}

Les fonctions acceptées par le thunk middleware sont appelées des thunks, d’où le nom du middleware. On peut maintenant utiliser le thunk défini ci-dessus comme suit dans nos composants :

function Freelances() {
    const dispatch = useDispatch()
    useEffect(() => {
        dispatch(fetchOrUpdateFreelances)
    }, [dispatch])
    // ...
}

Transformez vos actions en thunks à l’aide des thunk creators

Maintenant que c’est Redux-Thunk qui exécute les actions asynchrones, comment faire pour passer des paramètres ?

Lorsqu’un thunk a besoin d’un paramètre autre que dispatch  et getState  , il faut passer par un thunk creator !

Prenons, par exemple, la fonction fetchOrUpdateFreelance  :

export async function fetchOrUpdateFreelance(store, freelanceId) {
  // ...
}

Si on transforme cette fonction en thunk, on perd le paramètre freelanceId  ; il va donc falloir créer une fonction qui retourne un thunk :

export function fetchOrUpdateFreelance(freelanceId) {
    // on retourne un thunk
    return async (dispatch, getState) => {
        // ...
        const selectFreelanceById = selectFreelance(freelanceId)
        const status = selectFreelanceById(getState()).status
        if (status === 'pending' || status === 'updating') {
            return
        }
        dispatch(freelanceFetching(freelanceId))
        try {
            const response = await fetch(
                `http://localhost:8000/freelance?id=${freelanceId}`
            )
            const data = await response.json()
            dispatch(freelanceResolved(freelanceId, data))
        } catch (error) {
            dispatch(freelanceRejected(freelanceId, error))
        }
    }
}

Tout comme pour les action creators, il faudra exécuter le thunk creator pour l’envoyer à dispatch  :

function Profile() {
    const dispatch = useDispatch();
    useEffect(() => {
        dispatch(fetchOrUpdateFreelance(freelanceId))
    }, [dispatch, freelanceId])
    //...
}

Je vous propose de mettre tout cela en pratique dans la vidéo qui suit :

Vous pouvez retrouver le code de cette vidéo sur la branche P4C2S3-thunks du repository à cette adresse.

Exercez-vous

Prêt à mettre en pratique ? Dans cet exercice, vous allez devoir :

  • Utiliser les thunks pour les actions asynchrones existantes (  fetchOrUpdateFreelance  ,  fetchOrUpdateFreelances  , fetchOrUpdateSurvey  ).

  • Déplacer dans Redux la logique qui permet de récupérer les résultats du quiz (en utilisant Redux-Toolkit et les thunks, bien évidemment !)

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

Vous trouverez également ci-dessous une vidéo d’explication du code de la correction.

En résumé

  • Les middlewares permettent d’étendre les fonctionnalités de Redux.

  • Redux-Thunk est le middleware le plus populaire. Il permet de faciliter la gestion des actions asynchrones.

  • Un thunk est une fonction qui reçoit dispatch  et getState  en paramètre. Avec Redux-Thunk, on peut envoyer les thunks directement à la fonction dispatch  du store !

Nous avons vu dans un chapitre précédent les fonctions createAction  et createReducer  de Redux-Toolkit, qui permettent de simplifier l’écriture du code Redux. Dans le prochain chapitre, nous allons découvrir la fonctionnalité de slice de Redux-Toolkit, qui permet de réduire encore plus le boilerplate de la logique Redux !

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