Gestion de l'état dans React : un exemple pratique

réagir
gestion de l'état
API contextuelle
Gestion de l'état dans React : un exemple pratique cover image

React est le framework incontournable pour créer des applications dynamiques côté client pour de nombreux développeurs. La nature dynamique de ces applications vient de la flexibilité et de la liste étendue de capacités et de fonctionnalités possibles côté client, qui ont permis aux développeurs de créer des applications à part entière se chargeant sur le navigateur en quelques secondes, un exploit qui n'était pas le cas. possible (ou très fastidieux) à l’époque du Web statique.

Avec cette expansion des possibilités, est né le concept de gestion de l'état. À mesure que la complexité augmente dans les applications côté client, la nécessité de conserver l'état local devient un goulot d'étranglement en soi s'il n'est pas géré correctement et conçu dans un souci d'évolutivité.

Ce problème a été abordé par de nombreux frameworks, suivant différentes approches et en se concentrant sur différents ensembles de sous-problèmes. C'est pourquoi il est important d'avoir une compréhension de haut niveau de l'écosystème du framework de choix pour évaluer les besoins de chaque application et utiliser la bonne approche en fonction de ceux-ci. métrique. Cet article vous donnera un bref aperçu des problèmes courants de gestion d'état et tentera d'introduire différentes approches (useState, Context API) en réponse à ceux-ci. Bien que cet article présente plusieurs solutions, il se concentrera uniquement sur des défis à plus petite échelle. Nous aborderons des sujets plus avancés dans les prochains articles.

Flux de travail d'authentification

Le code présenté tout au long de l'article peut être trouvé ici.

Un lien d'aperçu en direct est accessible ici.

Prenons le cas où nous implémentons le processus d'authentification pour une application React.

User Login

Comme le montre le GIF ci-dessus, nous souhaitons permettre aux utilisateurs de se connecter ou de s'inscrire à notre application en utilisant leurs informations d'identification. Si des informations d'identification valides ont été fournies, l'utilisateur sera connecté, l'application naviguera automatiquement vers la page d'accueil et l'utilisateur pourra continuer à utiliser l'application.

De même, si l'utilisateur se déconnecte, les ressources de la page d'accueil seront protégées derrière la connexion, la page de connexion sera la seule page accessible par l'utilisateur.

En pensant à ce workflow en termes de mise en œuvre, nous aurions un composant principal nommé App, le composant App redirigera l'utilisateur vers l'une des deux pages : Accueil ou Connexion, l'état actuel de l'utilisateur (connecté, déconnecté) dictera laquelle page vers laquelle l'utilisateur est redirigé, un changement dans l'état actuel de l'utilisateur (par exemple passer de connecté à déconnecté) devrait déclencher une redirection instantanée vers la page correspondante.

State

Comme le montre l'illustration ci-dessus, nous souhaitons que le composant App prenne en compte l'état actuel et n'affiche qu'une des deux pages – Accueil ou Connexion – en fonction de cet état actuel.

Si l'utilisateur est nul, cela signifie que nous n'avons aucun utilisateur authentifié, nous accédons donc automatiquement à la page de connexion et protégeons la page d'accueil à l'aide du rendu conditionnel. Si l'utilisateur existe, nous faisons l'inverse.

Maintenant que nous avons une solide compréhension de ce qui doit être implémenté, explorons quelques options, mais commençons par mettre en place notre projet React,

Le dépôt du projet contient un exemple d'application backend que nous utiliserons pour implémenter le côté frontend (nous n'entrerons pas dans les détails car ce n'est pas l'objectif principal ici, mais le code a été intentionnellement gardé simple afin que vous n'ayez pas de mal à l'utiliser. )

Nous commençons par créer les pages et composants suivants :

  • Page d'accueil

  • Page de connexion

  • Modifier la page utilisateur

  • Composant de barre de navigation

  • Composant UserDropdown

Une application React avec plusieurs pages nécessite une navigation appropriée, pour cela, nous pouvons utiliser le react-router-dom pour créer un contexte de routeur de navigateur global et enregistrer différentes routes React.


yarn add react-router-dom

Nous savons que de nombreux lecteurs préfèrent suivre des didacticiels, alors voici le modèle de démarrage pour vous mettre au courant. Cette branche de démarrage utilise DaisyUI pour les composants TailwindCSS JSX prédéfinis. Il comprend tous les composants, pages et le routeur déjà configurés. Si vous envisagez de suivre ce didacticiel et de créer vous-même l'intégralité du flux d'authentification en suivant des étapes simples, commencez par créer le référentiel. Après avoir créé le référentiel, clonez-le et démarrez à partir de la start-herebranche:


git clone git@github.com:<your-username>/fullstack-resourcify.git

Une fois que vous avez extrait la branche start-here :

  • Ouvrez le projet avec votre éditeur de code préféré

  • Changer le répertoire en frontend/

  • Installer les dépendances : fil

  • Démarrer un serveur de développement : Yarn dev·

L'aperçu devrait ressembler à ceci :

Changing user name

Le nom affiché dans la barre de navigation – en haut à droite – est une variable d'état définie dans le composant principal de l'application. La même variable est transmise à la fois à la barre de navigation et à la page d'accueil. Le formulaire simple utilisé ci-dessus met en fait à jour la variable d'état « name » à partir du composant EditPage.

Les deux approches présentées ci-dessous entreront dans les détails de la mise en œuvre :

Première approche : useState

useState()

useState est l'un des hooks React les plus couramment utilisés, il vous permet de créer et de muter un état dans un composant React Functional. Le composant useState a une implémentation très simple et est facile à utiliser : pour créer un nouvel état, vous devez appeler useState avec la valeur initiale de votre état et le hook useState renverra un tableau contenant deux variables : la première est l'état. variable que vous pouvez utiliser pour référencer votre état, et la seconde une fonction que vous utilisez pour changer la valeur de l'état : assez simple.

Et si nous voyions cela en action ? Le nom affiché dans la barre de navigation – en haut à droite – est une variable d'état définie dans le composant principal de l'application. La même variable est transmise à la fois à la barre de navigation et à la page d'accueil. Le formulaire simple utilisé ci-dessus met en fait à jour la variable d'état « name » à partir du composant EditPage. L'essentiel est le suivant : useState est un hook principal qui accepte un état initial comme paramètre et renvoie deux variables contenant deux valeurs, la variable d'état contenant l'état initial et une fonction de définition pour la même variable d'état.

Décomposons cela et voyons comment cela a été mis en œuvre en premier lieu.

  1. Création de la variable d'état « nom » :

./src/App.jsx


import { useState } from "react";

function App() {
const testValue = "CLA";
 //using the useState hook to create a state variable out of an initial value passed as an argument
 const [name, setName] = useState(testValue);
 console.log(`Rendering: ${name}`);

 return (...)};

Accessoires

Les accessoires sont l'un des éléments de base d'un composant React. Conceptuellement, si vous considérez un composant fonctionnel React comme une fonction Javascript, alors les accessoires ne sont rien de plus que les paramètres de la fonction. La combinaison des accessoires et du hook useState peut vous offrir un cadre solide. pour gérer l'état dans une simple application React.

Les accessoires React sont transmis en tant qu'attributs aux composants personnalisés. Les attributs passés en tant qu'accessoires peuvent être déstructurés de l'objet props lors de son acceptation comme argument, comme ceci :

Passer des accessoires

<Routes>
  <Route path="/" element={<Home name={name} />} /> // Passing name as a prop to
  Home Component
  <Route
    path="/user"
    element={<EditUser name={name} setName={setName} />} // passing both name and setItem function as props to EditUser component
  />
  <Route path="/login" element={<Login />} />
</Routes>

Les accessoires peuvent être acceptés et utilisés dans un composant fonctionnel similaire aux arguments de fonction normaux. C'est parce que « nom » est passé comme accessoire au composant Home, que nous pouvons le restituer dans le même composant. Dans l'exemple suivant, nous acceptons la prop passée en utilisant la syntaxe de déstructuration pour extraire la propriété name de l'objet props.

Accepter les accessoires

./src/pages/Home.jsx

... ...
export default function Home({name}) { //Destructuring the name property from the props object, another approach would be: Home(props.name)
 console.log("Rendering: Home");
 return (
   <div className="flex flex-col bg-white m-auto p-auto">
     <h1 className="flex py-5 lg:px-20 md:px-10 mx-5 font-bold text-2xl text-gray-800">
       Welcome {name}
     </h1>
... ...

Conseil de pro

Ouvrez la console du navigateur et remarquez comment tous les composants utilisant la prop « nom » sont restitués lorsque l'état change. Lors de la manipulation d'une variable d'état, React stockera l'état suivant, restituera à nouveau votre composant avec les nouvelles valeurs et mettra à jour l'interface utilisateur.

Components Re-rendering

Inconvénients de useState

Accessoires-Forage

Le forage d'accessoires est un terme désignant une hiérarchie de composants dans laquelle un ensemble de composants a besoin de certains accessoires fournis par un composant parent. Une solution de contournement courante que les développeurs inexpérimentés utilisent habituellement consiste à transmettre ces accessoires tout au long de la chaîne de composants. Le problème avec cela L'approche est qu'un changement dans l'un de ces accessoires déclenchera le nouveau rendu de toute la chaîne de composants, ralentissant ainsi l'ensemble de l'application en raison de ces rendus inutiles, les composants au milieu de la chaîne qui ne nécessitent pas ces accessoires. agir comme moyen de transfert d'accessoires.

Exemple :

  • Une variable d'état a été définie dans le composant principal de l'application à l'aide du hook useState()

  • Un accessoire de nom a été passé au composant Navbar

  • Même accessoire accepté dans Navbar et transmis une fois de plus comme accessoire au composant UserDropdown

  • UserDropdown est le dernier élément enfant qui accepte le prop.

Prop-drilling

./src/App.jsx

... ...
function App() {
 const [name, setName] = useState(test);
 console.log("Rendering: App");

 return (
   <BrowserRouter>
     <div className="h-screen">
       <Navbar name={name} />

       <main className="px-4">
... ...

./src/components/Navbar.jsx

import React from "react";
import Logo from "../assets/cla.svg";
import { BiSearchAlt } from "react-icons/bi";
import { Link } from "react-router-dom";
import UserDropdown from "./UserDropdown";

export default function Navbar({ name }) {
  console.log("Rendering: Navbar");

  return (
    <>
      <div className="navbar bg-base-100 drop-shadow-sm">
        <div className="flex-1">
          <Link
            to="/"
            className="btn btn-ghost normal-case text-md md:text-xl px-2 gap-1"
          >
            <img src={Logo} className="h-6" alt="" />
            Resources
          </Link>
        </div>

        <UserDropdown name={name} />
      </div>
    </>
  );
}

Imaginez à quel point il est difficile de maintenir une application React avec différentes couches de composants passant et rendant le même état.

Complexité croissante et qualité du code

Avec l'utilisation de useState et des accessoires comme seul moyen de gestion de l'état dans une application React, la base de code peut rapidement devenir complexe, devant gérer des dizaines ou des centaines de variables d'état, qui peuvent être des doublons les unes des autres, dispersées dans différents fichiers et Les composants peuvent être assez intimidants, toute modification d'une variable d'état donnée nécessitera un examen attentif des dépendances entre les composants pour éviter tout re-rendu supplémentaire potentiel dans une application déjà lente.

Deuxième approche : API contextuelle

L'API de contexte est la tentative de React de résoudre les inconvénients de l'utilisation uniquement des accessoires et de useState pour la gestion de l'état. En particulier, l'API de contexte constitue une réponse au problème mentionné précédemment concernant la nécessité de transmettre les accessoires dans l'ensemble de l'arborescence des composants. Grâce à l'utilisation du contexte, vous pouvez définir un état pour les données que vous jugez globales et accéder à son état à partir de n'importe quel point de l'arborescence des composants : plus besoin de forage accessoire.

Il est important de souligner que l'API contextuelle a été initialement conçue pour résoudre le problème du partage global de données (données telles que les thèmes de l'interface utilisateur, les informations d'authentification qui sont notre cas d'utilisation, les langues, etc.), pour d'autres types de données qui doivent être partagées. parmi plusieurs composants mais n'est pas nécessairement global pour toute l'application, le contexte n'est peut-être pas la meilleure option, selon le cas d'utilisation, vous pouvez envisager d'autres techniques telles que la composition des composants, ce qui sort du cadre de cet article.

Passer au contexte

Création du contexte d'authentification

createContext est simple, il crée une nouvelle variable de contexte, il prend un seul paramètre facultatif : la valeur par défaut de la variable de contexte.

Voyons cela en action, créez d'abord un nouveau dossier « contextes » et un nouveau fichier à l'intérieur « Auth.jsx ». Pour créer un nouveau contexte, nous devons invoquer la fonction createContext(), attribuer la valeur renvoyée à une nouvelle variable Auth qui sera ensuite exportée :

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

Fournissez le contexte d'authentification

Nous devons maintenant exposer la variable de contexte "Auth" que nous avons créée précédemment. Pour y parvenir, nous utilisons le fournisseur de contexte. Le fournisseur de contexte est un composant qui a un accessoire "valeur", nous pouvons utiliser cet accessoire pour partager une valeur - nom. – dans l'arborescence des composants, en encapsulant l'arborescence des composants avec le composant fournisseur, nous rendons cette valeur accessible depuis n'importe où dans l'arborescence des composants, sans avoir besoin de transmettre cet accessoire à chaque composant enfant individuellement.

Dans notre exemple d'authentification, nous avons besoin d'une variable pour contenir l'objet utilisateur et d'une sorte de setter pour manipuler cette variable d'état, c'est un cas d'utilisation parfait pour useState. Lorsque vous utilisez Context, vous devez vous assurer que vous définissez les données que vous souhaitez fournir et que vous transmettez ces données (utilisateur dans notre exemple) à toute l'arborescence des composants imbriquée à l'intérieur. Définissez donc une nouvelle variable d'état utilisateur dans le composant fournisseur et enfin, nous transmettons à la fois user et setUser dans un tableau comme valeur que le fournisseur exposera à l'arborescence des composants :

./src/contexts/Auth.jsx

import { createContext, useState } from "react";

export const Auth = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  return <Auth.Provider value={[user, setUser]}>{children}</Auth.Provider>;
};

Une autre chose que nous pouvons faire est de déplacer notre variable d'état « nom » du composant principal de l'application vers le contexte Auth et de l'exposer aux composants imbriqués :

import { createContext, useState } from "react";

export const Auth = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [name, setName] = useState("CLA");
  return <Auth.Provider value={[name, setName]}>{children}</Auth.Provider>;
};

Il ne reste plus qu'à imbriquer notre application dans le même composant AuthProvider que nous venons d'exporter.

./src/main.jsx :

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { AuthProvider } from "./contexts/Auth";

import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <AuthProvider>
    <App />
  </AuthProvider>
);

Étant donné que nous rendons le prop enfants dans Auth.Provider, tous les éléments imbriqués dans le composant AuthProvider peuvent désormais consommer la valeur prop que nous avons transmise à Auth.Provider. Cela peut sembler déroutant, mais une fois que vous l’avez expérimenté, essayez de fournir et de consommer un état – contextuel – global. Après tout, cela n'avait de sens pour moi qu'après avoir expérimenté l'API Context.

Consommation du contexte d'authentification

La dernière étape est simple, nous utilisons le hook contextuel "useContext" pour accéder à la valeur fournie par le fournisseur de contexte de "Auth", qui dans notre cas est le tableau contenant user et setUser, dans le code suivant, nous pouvons utilisez useContext pour consommer le contexte Auth dans la barre de navigation. Cela n'est possible que parce que Navbar est imbriqué dans le composant App, et comme AuthProvider entoure le composant App, la prop value peut être consommée à l'aide du hook useContext uniquement. Un autre outil génial que React fournit prêt à l'emploi pour gérer toutes les données accessibles à l'échelle mondiale et également manipulées par n'importe quel composant consommateur.

./src/components/Navbar.jsx

export default function Navbar() {
 const [name, setName] = useContext(Auth);
 console.log(name)

 return (...)};

Remarquez que nous n'acceptons plus aucun accessoire dans le composant fonctionnel Navbar(). Nous utilisons useContext(Auth) pour consommer le contexte Auth à la place, en récupérant à la fois name et setName. Cela signifie également que nous n'avons plus besoin de transmettre le prop à Navbar :

./src/App.jsx

// ... //
return (
   <BrowserRouter>
     <div className="h-screen">
       <Navbar/> // no need to pass prop anymore
// ... //

Mise à jour du contexte d'authentification

Nous pouvons également utiliser la fonction setName fournie pour manipuler la variable d'état « name » :

./src/pages/EditUser.jsx

export default function EditUser() { // no need to accept props anymore
 const [name, setName] = useContext(Auth); // grabbing the name and setName variables from Auth context
 console.log("Rendering: EditUser");

Présentation du crochet useReducer()

Dans l'exemple précédent, nous avons utilisé l'API de contexte pour gérer et partager notre état dans l'arborescence des composants. Vous avez peut-être remarqué que nous utilisons toujours useState comme base de notre logique, et c'est généralement correct car notre état est toujours un simple objet. à ce stade, mais si nous devions étendre les capacités de notre flux d'authentification, nous devrons certainement stocker plus que l'e-mail de l'utilisateur actuellement connecté, et c'est là que nous revenons aux limitations que nous avions précédemment introduites en ce qui concerne l'utilisation de useState avec un état complexe, heureusement, React résout ce problème en proposant une alternative à useState pour gérer l'état complexe : entrez useReducer.

useReducer peut être considéré comme une version généralisée de useState, il prend deux paramètres : une fonction de réduction et un état initial.

Rien d'intéressant à noter pour l'état initial, la magie opère à l'intérieur de la fonction réducteur : elle vérifie le type d'action qui s'est produite, et en fonction de cette action, le réducteur déterminera les mises à jour à appliquer à l'état et retournera sa nouvelle valeur .

En regardant le code ci-dessous, la fonction de réduction a deux types d'actions possibles :

  • "LOGIN" : auquel cas l'état de l'utilisateur sera mis à jour avec les nouveaux identifiants utilisateur fournis dans la charge utile de l'action.

  • "LOGOUT" : auquel cas l'état de l'utilisateur sera supprimé du stockage local et remis à null.

Il est important de noter que l'objet d'action contient à la fois un champ de type qui détermine la logique à appliquer et un champ de charge utile facultatif pour fournir les données nécessaires à l'application de cette logique.

Enfin, le hook useReducer renvoie l'état actuel et une fonction de répartition que nous utilisons pour transmettre une action au réducteur.

Pour le reste de la logique, c'est identique à l'exemple précédent :

./src/contexts/Auth.jsx

import { createContext, useEffect, useReducer, useState } from "react";

export const Auth = createContext();
import { createContext, useReducer } from "react";

export const Auth = createContext();
const reducer = (state, action) => {
  switch (action.type) {
    case "LOGIN":
      return { user: action.payload };

    case "LOGOUT":
      localStorage.removeItem("user");
      return { user: null };

    default:
      break;
  }
};

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  return (
    <Auth.Provider value={{ ...state, dispatch }}>{children}</Auth.Provider>
  );
};

Répartir des actions au lieu d'utiliser la fonction setter setState – par exemple : setName –

Comme nous venons de le mentionner, nous utilisons la fonction de répartition pour transmettre une action au réducteur, dans le code suivant, nous lançons une action LOGIN et nous fournissons l'e-mail de l'utilisateur comme charge utile, maintenant l'état de l'utilisateur sera mis à jour et ce changement se déclenchera un nouveau rendu de tous les composants abonnés à l'état utilisateur. Il est important de souligner qu'un nouveau rendu ne sera déclenché que si un changement réel d'état se produit, aucun nouveau rendu si le réducteur renvoie le même état précédent.

export default function Login() {
 const { dispatch } = useContext(Auth);


 const handleLogin = async (e) => {
   // Updating the global Auth context
   dispatch({ type: "LOGIN", payload: {email: email.current.value} });
 };

 return (...)};

Utilisateur en ligne

JWT Local storage

Conseil de pro

Remarquez comment l'objet utilisateur que nous recevons après une connexion réussie est désormais stocké dans localStorage.

Hook personnalisé pour la connexion

Maintenant que nous maîtrisons useReducer, nous pouvons encapsuler davantage notre logique de connexion et de déconnexion dans leurs propres hooks personnalisés séparés, par un seul appel au hook de connexion, nous pouvons gérer un appel API vers la route de connexion, récupérer le nouvel utilisateur. informations d'identification et les stocker dans le stockage local, envoyer un appel LOGIN pour mettre à jour l'état de l'utilisateur, tout en gérant les erreurs :

./src/hooks/useLogin.jsx

import { useContext, useState } from "react";
import { Auth } from "../contexts/Auth";

export const useLogin = () => {
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  const { dispatch } = useContext(Auth);

  const login = async (email, password) => {
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch("/api/users/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });

      const json = await response.json();

      if (json.name === "Error") {
        setError(json.message);
        setIsLoading(false);
      }

      if (!response.ok) {
        setIsLoading(false);
        setError(json);
      }

      if (response.ok) {
        // Save the user and token in the localstorage
        localStorage.setItem("user", JSON.stringify(json));

        // Updating the global Auth context
        dispatch({ type: "LOGIN", payload: json });

        setIsLoading(false);
      }
    } catch (error) {
      console.log(error);
    }
  };

  return { error, isLoading, login };
};

Remarque : pour les utilisateurs React plus avancés parmi les lecteurs, vous vous demandez peut-être pourquoi n'avons-nous pas utilisé la fonction d'initialisation paresseuse de useReducer pour récupérer les informations d'identification de l'utilisateur, useReducer accepte un troisième paramètre facultatif appelé fonction init, cette fonction est utilisée au cas où nous devons appliquer une certaine logique avant de pouvoir obtenir la valeur initiale de l'état, la raison pour laquelle nous n'avons pas opté pour cela est une simple question de séparation des préoccupations, le code de cette façon est plus simple à comprendre et par conséquent plus simple à maintenir .

Page de connexion

Voici à quoi ressemble la partie supérieure de notre page de connexion après avoir utilisé le hook useLogin() pour extraire la fonction de connexion et invoqué la fonction de connexion avec les informations d'identification soumises par un utilisateur :

// ... ... //
export default function Login() {
 const { login, isLoading, error } = useLogin();
 console.log("Rendering: Login");
 const email = createRef(null);
 const password = createRef(null);

 const handleLogin = async (e) => {
   await login(email.current.value, password.current.value);
 };
 return (...)
// ... ... //

Nous désactivons également la fonction Soumettre lorsque l'utilisateur soumet le formulaire :

<button
  onClick={handleLogin}
  disabled={isLoading}
  className="btn btn-square w-full bg-gray-100 text-gray-600 hover:bg-gray-300 border-none"
>
  {isLoading && "A moment please!"}
  {!isLoading && "Login"}
</button>

Et en affichant toutes les erreurs d'authentification que nous recevons de notre backend :

{
  error && <span className="text-red-500 p-2">{error.message}</span>;
}

Rendering Errors

Maintenir l'état de l'utilisateur

Vous vous demandez peut-être pourquoi nous devons stocker l'objet utilisateur dans localStorage. En termes simples, nous voulons garder l'utilisateur connecté tant que le jeton n'a pas expiré. Utiliser localStorage est un excellent moyen de stocker des morceaux de JSON, tout comme dans notre exemple. Remarquez comment l'état est effacé si vous actualisez la page après la connexion. Cela peut être résolu facilement à l'aide d'un hook useEffect pour vérifier si nous avons un objet utilisateur stocké dans localStorage, s'il y en a un, nous connectons l'utilisateur automatiquement :

// ... ... //
export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  useEffect(() => {
    const user = JSON.parse(localStorage.getItem("user"));

    if (user) {
      return dispatch({ type: "LOGIN", payload: user });
    }
  }, []);

  return (
    <Auth.Provider value={{ ...state, dispatch }}>{children}</Auth.Provider>
  );
};

Hooks personnalisés pour la déconnexion

La même chose s'applique avec le hook Logout, nous envoyons ici une action LOGOUT pour supprimer les informations d'identification de l'utilisateur actuel de l'état et du stockage local :

./src/hooks/useLogout.jsx

import { useContext } from "react";
import { Auth } from "../contexts/Auth";

export const useLogout = () => {
  const { dispatch } = useContext(Auth);

  const logout = () => {
    // delete user from the localstorage
    localStorage.removeItem("user");
    // Wipe out the Auth context (user:null) / dipatch 'LOGOUT'
    dispatch({ type: "LOGOUT" });
  };

  return { logout };
};

Déconnexion de l'utilisateur

Pour déconnecter un utilisateur, ajoutons un événement click au bouton Logout trouvé dans UserDropdown.jsx et gérons-le en conséquence :

./src/components/UserDropdown.jsx

// Extracting the logout function from useLogout() and handling the click event listener //
export default function UserDropdown() {
 const { user } = useContext(Auth);
 const { logout } = useLogout();
 console.log("Rendering: UserDropdown");

 const handleLogout = () => {
   logout();
 };
// ... ... //

// Adding a click event listener to logout button //
<li>
   <button onClick={handleLogout}>Logout</button>
</li>
// ... ... //

User Logout

Protéger les routes de réaction

La dernière étape de la mise en œuvre de notre application consiste à exploiter l'état global de l'utilisateur pour contrôler la navigation de l'utilisateur, un rappel rapide du comportement que nous devons adopter : initialement, l'utilisateur est accueilli par la page de connexion, à partir de ce moment, l'utilisateur ne peut accéder qu'à la page d'accueil. après une connexion réussie, de la même manière, l'utilisateur sera redirigé vers la page de connexion lors d'une déconnexion.

Nous y parvenons à l'aide de la bibliothèque react-router-dom en définissant 2 routes : "/" et "/login", nous contrôlons quel composant rendre sur chaque route en utilisant l'état d'authentification global, auth évalué à null représente un utilisateur non authentifié et vice-versa :

./src/App.jsx

import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import "./App.css";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";
import Login from "./pages/Login";
import { useContext } from "react";
import { Auth } from "./contexts/Auth";

function App() {
  const { user } = useContext(Auth);
  return (
    <BrowserRouter>
      <div className="h-screen">
        <Navbar />
        <main className="px-4">
          <Routes>
            <Route
              path="/"
              element={user ? <Home /> : <Navigate to="/login" />}
            />
            <Route
              path="/login"
              element={!user ? <Login /> : <Navigate to="/" />}
            />
          </Routes>
        </main>
      </div>
    </BrowserRouter>
  );
}

export default App;

Diagramme récapitulatif

Diagram

Conclure

Dans cet article, nous avons essayé d'aborder le cas d'utilisation simple mais très courant de la mise en œuvre de la gestion d'état pour un flux de travail d'authentification, en passant en revue les différentes approches, la justification de chacune et leurs compromis. La gestion de l'état dans les frameworks côté client et dans React en particulier est l'un des sujets les plus abordés dans la communauté frontend, tout simplement parce qu'il peut améliorer ou défaire les performances et l'évolutivité de votre application. La quantité de techniques, de modèles, de bibliothèques et d'outils qui tentent de résoudre ce problème de gestion d'état est écrasante. Notre objectif était de vous donner une solide compréhension des pratiques afin que vous puissiez les implémenter dans votre propre application, pour des applications plus avancées. techniques et modèles de gestion d'état dans des applications plus complexes, consultez notre prochain article où nous abordons le redux pour une gestion d'état évolutive dans React.

À venir

ContextAPI contre la boîte à outils Redux

Redux a été adopté par la communauté pendant de nombreuses années avant la boîte à outils Redux. En fait, RTK a été introduit comme modèle de démarrage pour amorcer la gestion de l'état Redux dans les nouvelles applications (son nom initial était « redux-starter-kit » en octobre 2019), bien que aujourd'hui, il existe un consensus général entre les responsables de Redux et la communauté sur le fait que la boîte à outils Redux est le moyen valable de travailler avec redux. RTX fait abstraction d'une grande partie de la logique Redux, ce qui le rend plus facile à utiliser, avec beaucoup moins de détails, et encourage les développeurs à suivez les meilleures pratiques, l'une des principales différences entre les deux est que Redux a été conçu pour être sans opinion, fournissant une API minimale et s'attendant à ce que les développeurs fassent le gros du travail en écrivant leurs propres bibliothèques pour les tâches courantes et en gérant la structure du code, cela a entraîné un temps de développement lent et un code compliqué, la boîte à outils Redux a été ajoutée comme couche supplémentaire d'abstraction empêchant les développeurs de tomber dans ses pièges courants, reportez-vous à la documentation officielle pour plus d'informations et le raisonnement de ses responsables ici.


Career Services background pattern

Services de carrière

Contact Section background image

Restons en contact

Code Labs Academy © 2025 Tous droits réservés.