Gestione dello Stato in React: un esempio pratico

reagire
gestione dello stato
API di contesto
Gestione dello Stato in React: un esempio pratico cover image

React è il framework di riferimento per la creazione di applicazioni dinamiche lato client per molti sviluppatori. La natura dinamica di queste applicazioni deriva dalla flessibilità e dall'elenco esteso di capacità e caratteristiche possibili sul lato client che hanno consentito agli sviluppatori di creare applicazioni complete caricandole sul browser in pochi secondi, un'impresa che non era possibile (o molto ingombrante) ai tempi del web statico.

Con questa espansione delle possibilità, è arrivato il concetto di gestione dello stato, man mano che la complessità aumenta nelle applicazioni lato client, la necessità di mantenere lo stato locale cresce fino a diventare di per sé un collo di bottiglia se non gestito correttamente e concepito pensando alla scalabilità.

Questo problema è stato affrontato da molti framework, seguendo approcci diversi e concentrandosi su diversi insiemi di sottoproblemi, ecco perché è importante avere una comprensione di alto livello dell'ecosistema del framework scelto per valutare le esigenze di ciascuna applicazione e utilizzare l'approccio giusto seguendo quelle metrica. Questo articolo ti fornirà una breve panoramica dei problemi comuni di gestione dello stato e proverà a introdurre diversi approcci (useState, Context API) come risposta ad essi. Sebbene questo articolo presenterà molteplici soluzioni, si concentrerà solo su sfide su scala ridotta, tratteremo argomenti più avanzati nei prossimi articoli.

Flusso di lavoro di autenticazione

Il codice mostrato in tutto l'articolo può essere trovato qui.

È possibile accedere a un collegamento all'anteprima dal vivo qui.

Considera il caso in cui implementiamo il processo di autenticazione per un'applicazione React.

User Login

Come mostrato nella GIF sopra, vogliamo consentire agli utenti di accedere o registrarsi alla nostra applicazione utilizzando le credenziali. Se sono state fornite credenziali valide, l'utente verrà loggato, l'applicazione passerà automaticamente alla home page e l'utente potrà procedere all'utilizzo dell'applicazione.

Allo stesso modo, se l'utente si disconnette, le risorse della home page saranno protette tramite login, la pagina di login sarà l'unica pagina accessibile dall'utente.

Pensando a questo flusso di lavoro in termini di implementazione, avremmo un componente principale denominato App, il componente App reindirizzerà l'utente a una delle due pagine: Home o Login, lo stato corrente dell'utente (loggato, disconnesso) determinerà quale pagina a cui l'utente viene reindirizzato, un cambiamento nello stato corrente dell'utente (ad esempio il passaggio da connesso a disconnesso) dovrebbe attivare un reindirizzamento istantaneo alla pagina corrispondente.

State

Come mostrato nell'illustrazione sopra, vogliamo che il componente App consideri lo stato corrente e visualizzi solo una delle due pagine – Home o Login – in base a tale stato corrente.

Se l'utente è nullo, significa che non abbiamo alcun utente autenticato, quindi accediamo automaticamente alla pagina di accesso e proteggiamo la home page utilizzando il rendering condizionale. Se l'utente esiste, facciamo il contrario.

Ora che abbiamo una solida comprensione di ciò che dovrebbe essere implementato, esploriamo alcune opzioni, ma prima impostiamo il nostro progetto React,

Il repository del progetto contiene un'applicazione backend di esempio che useremo per implementare il lato frontend (non entreremo in questo argomento perché non è l'obiettivo principale qui ma il codice è stato intenzionalmente mantenuto semplice quindi non avrai difficoltà ad usarlo )

Iniziamo creando le seguenti pagine e componenti:

  • Pagina iniziale

  • Pagina di login

  • Modifica pagina utente

  • Componente barra di navigazione

  • Componente UserDropdown

Un'applicazione React con più pagine necessita di una navigazione adeguata, per questo possiamo utilizzare react-router-dom per creare un contesto router browser globale e registrare diversi percorsi React.


yarn add react-router-dom

Sappiamo che molti lettori preferiscono seguire i tutorial, quindi ecco il modello iniziale per tenerti aggiornato. Questo ramo iniziale utilizza DaisyUI per i componenti JSX TailwindCSS predefiniti. Include tutti i componenti, le pagine e il router già configurati. Se stai pensando di seguire questo tutorial, costruendo da solo l'intero flusso di autenticazione seguendo semplici passaggi, inizia prima con il fork del repository. Dopo aver eseguito il fork del repository, clonalo e inizia dal start-herebranch:


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

Una volta tirato il ramo start-here:

  • Apri il progetto con il tuo editor di codice preferito

  • Cambia la directory in frontend/

  • Installa le dipendenze: filato

  • Avvia un server di sviluppo: Yarn Dev·

L'anteprima dovrebbe assomigliare a questa:

Changing user name

Il nome visualizzato nella barra di navigazione, in alto a destra, è una variabile di stato definita nel componente principale dell'app. La stessa variabile viene passata sia alla Navbar che alla Home page. Il semplice modulo utilizzato sopra aggiorna effettivamente la variabile di stato "nome" dal componente EditPage.

I due approcci presentati di seguito entreranno nei dettagli dell’implementazione:

Primo approccio: useState

useState()

useState è uno degli hook React più comunemente utilizzati, consente di creare e modificare lo stato in un componente React Functional. Il componente useState ha un'implementazione davvero semplice ed è facile da usare: per creare un nuovo stato, devi chiamare useState con il valore iniziale del tuo stato e l'hook useState restituirà un array contenente due variabili: la prima è lo stato variabile che puoi utilizzare per fare riferimento al tuo stato, e la seconda una funzione che usi per modificare il valore dello stato: piuttosto semplice.

Che ne dici di vederlo in azione? Il nome visualizzato nella barra di navigazione, in alto a destra, è una variabile di stato definita nel componente principale dell'app. La stessa variabile viene passata sia alla Navbar che alla Home page. Il semplice modulo utilizzato sopra aggiorna effettivamente la variabile di stato "nome" dal componente EditPage. La conclusione è questa: useState è un hook principale che accetta uno stato iniziale come parametro e restituisce due variabili che contengono due valori, la variabile di stato contenente lo stato iniziale e una funzione setter per la stessa variabile di stato.

Analizziamolo e vediamo come è stato implementato in primo luogo.

  1. Creazione della variabile di stato "nome":

./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 (...)};

Oggetti di scena

I props sono uno degli elementi costitutivi di base di un componente react, concettualmente, se pensi a un componente funzionale React come una funzione Javascript, allora i props non sono altro che i parametri della funzione, combinando props e l'hook useState puoi offrirti una solida struttura per la gestione dello stato attraverso una semplice applicazione React.

Le proprietà di reazione vengono passate come attributi ai componenti personalizzati. Gli attributi passati come oggetti di scena possono essere destrutturati dall'oggetto oggetti di scena quando lo si accetta come argomento, in modo simile a questo:

Passaggio di oggetti di scena

<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>

Gli oggetti di scena possono essere accettati e utilizzati all'interno di un componente funzionale in modo simile ai normali argomenti di funzione. È perché "nome" viene passato come oggetto di scena al componente Home, che possiamo renderizzarlo nello stesso componente. Nell'esempio seguente, accettiamo la prop passata utilizzando la sintassi di destrutturazione per estrarre la proprietà name dall'oggetto props.

Accettare oggetti di scena

./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>
... ...

Suggerimento da professionista

Apri la console del browser e nota come tutti i componenti che utilizzano la prop "name" vengono nuovamente renderizzati quando lo stato cambia. Quando si manipola una variabile di stato, React memorizzerà lo stato successivo, eseguirà nuovamente il rendering del componente con i nuovi valori e aggiornerà l'interfaccia utente.

Components Re-rendering

##Inconvenienti dello stato di utilizzo

Puntelli-Perforazione

La perforazione degli oggetti di scena è un termine per riferirsi a una gerarchia di componenti in cui un insieme di componenti necessita di determinati oggetti di scena forniti da un componente principale, una soluzione comune utilizzata solitamente dagli sviluppatori inesperti è quella di passare questi oggetti di scena attraverso l'intera catena di componenti, il problema con questo L'approccio è che un cambiamento in uno qualsiasi di questi oggetti di scena attiverà il re-rendering dell'intera catena di componenti, rallentando di fatto l'intera applicazione come risultato di questi rendering non necessari, i componenti al centro della catena che non richiedono questi oggetti di scena fungere da mezzo per trasferire oggetti di scena.

Esempio:

  • Una variabile di stato è stata definita nel componente principale dell'app utilizzando l'hook useState()

  • È stata passata una prop del nome al componente Navbar

  • Stesso oggetto accettato in Navbar e passato ancora una volta come oggetto al componente UserDropdown

  • UserDropdown è l'ultimo elemento figlio che accetta la 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>
    </>
  );
}

Immagina quanto sia difficile mantenere un'app React con diversi livelli di componenti che passano e restituiscono tutti lo stesso stato.

Crescente complessità e qualità del codice

Con l'uso di useState e props come unico mezzo di gestione dello stato in un'applicazione react, la base di codice può crescere rapidamente in complessità, dovendo gestire decine o centinaia di variabili di stato, che possono essere duplicati l'una dell'altra, sparse su file diversi e I componenti possono essere piuttosto scoraggianti, qualsiasi modifica a una determinata variabile di stato richiederà un'attenta considerazione delle dipendenze tra i componenti per evitare qualsiasi potenziale ulteriore rendering in un'applicazione già lenta.

Secondo approccio: API di contesto

L'API Context è il tentativo di React di risolvere gli inconvenienti derivanti dall'uso esclusivo di props e useState per la gestione dello stato, in particolare, l'API context si presenta come una risposta al problema menzionato in precedenza riguardante la necessità di passare props lungo l'intero albero dei componenti. Con l'uso del contesto, puoi definire uno stato per i dati che ritieni globali e accedere al suo stato da qualsiasi punto nell'albero dei componenti: niente più prop-drilling.

È importante sottolineare che l'API di contesto è stata inizialmente concepita per risolvere il problema della condivisione globale dei dati (dati come temi dell'interfaccia utente, informazioni di autenticazione che sono il nostro caso d'uso, lingue e simili), per altri tipi di dati che devono essere condivisi tra più di un componente ma non è necessariamente globale per tutta l'applicazione, il contesto potrebbe non essere l'opzione migliore, a seconda del caso d'uso, potresti prendere in considerazione altre tecniche come composizione del componente che non rientra nell'ambito di questo articolo.

Passaggio al contesto

Creazione del contesto di autenticazione

createContext è semplice, crea una nuova variabile di contesto, accetta un unico parametro opzionale: il valore predefinito della variabile di contesto.

Vediamolo in azione, crea prima una nuova cartella “contexts” e un nuovo file al suo interno “Auth.jsx”. Per creare un nuovo contesto, dobbiamo invocare la funzione createContext(), assegnare il valore restituito ad una nuova variabile Auth che verrà successivamente esportata:

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

Fornire il contesto di autenticazione

Ora dobbiamo esporre la variabile di contesto "Auth" che abbiamo creato in precedenza, per raggiungere questo obiettivo utilizziamo il provider di contesto, il provider di contesto è un componente che ha una prop "valore", possiamo utilizzare questa prop per condividere un valore - nome – attraverso l'albero dei componenti, avvolgendo l'albero dei componenti con il componente fornitore rendiamo quel valore accessibile da qualsiasi punto all'interno dell'albero dei componenti, senza la necessità di trasmettere quel sostegno a ciascun componente figlio individualmente.

Nel nostro esempio di autenticazione, abbiamo bisogno di una variabile per contenere l'oggetto utente e di una sorta di setter per manipolare quella variabile di stato, questo è un caso d'uso perfetto per useState. Quando usi Context, devi assicurarti di definire i dati che desideri fornire e di passare tali dati (utente nel nostro esempio) a tutto l'albero dei componenti annidato all'interno, quindi definisci una nuova variabile di stato utente all'interno del componente provider e infine passiamo sia user che setUser all'interno di un array come valore che il provider esporrà all'albero dei componenti:

./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>;
};

Un'altra cosa che possiamo fare è spostare la nostra variabile di stato "name" dal componente principale dell'app al contesto Auth ed esporla a componenti nidificati:

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>;
};

Ora non resta che nidificare la nostra app nello stesso componente AuthProvider che abbiamo appena esportato.

./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>
);

Poiché stiamo eseguendo il rendering della prop child all'interno di Auth.Provider, tutti gli elementi annidati all'interno del componente AuthProvider sono ora in grado di consumare la value prop che abbiamo passato ad Auth.Provider. Potrebbe sembrare confuso, ma una volta sperimentato, prova a fornire e consumare uno stato globale di contesto. Dopotutto, per me aveva senso solo dopo aver sperimentato l'API Context.

Consumo del contesto di autenticazione

Il passaggio finale è semplice, utilizziamo l'hook di contesto "useContext" per accedere al valore fornito dal provider di contesto di "Auth", che nel nostro caso è l'array contenente user e setUser, nel codice seguente siamo in grado di utilizzare useContext per utilizzare il contesto Auth all'interno della Navbar. Ciò è possibile solo perché Navbar è annidato all'interno del componente App e poiché AuthProvider avvolge il componente App, la value prop può essere utilizzata utilizzando solo l'hook useContext. Un altro fantastico strumento che React fornisce immediatamente per gestire tutti i dati a cui è possibile accedere a livello globale e anche manipolare da qualsiasi componente del consumatore.

./src/components/Navbar.jsx

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

 return (...)};

Nota come non accettiamo più alcun oggetto di scena nel componente funzionale Navbar(). Stiamo invece utilizzando useContext(Auth) per consumare il contesto Auth, catturando sia name che setName. Ciò significa anche che non abbiamo più bisogno di passare l'elica a Navbar:

./src/App.jsx

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

Aggiornamento del contesto di autenticazione

Possiamo anche utilizzare la funzione setName fornita per manipolare la variabile di stato "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");

Presentazione dell'hook useReducer()

Nell'esempio precedente abbiamo utilizzato l'API di contesto per gestire e condividere il nostro stato lungo l'albero dei componenti, potresti aver notato che stiamo ancora utilizzando useState come base della nostra logica, e questo è per lo più ok poiché il nostro stato è ancora un oggetto semplice a questo punto, ma se dovessimo espandere le capacità del nostro flusso di autenticazione, avremo sicuramente bisogno di archiviare qualcosa di più della semplice email dell'utente attualmente loggato, ed è qui che torniamo alle limitazioni che avevamo in precedenza riguardo a l'uso di useState con stato complesso, fortunatamente, React risolve questo problema fornendo un'alternativa a useState per la gestione dello stato complesso: inserisci useReducer.

useReducer può essere pensato come una versione generalizzata di useState, richiede due parametri: una funzione riduttore e uno stato iniziale.

Niente di interessante da notare per lo stato iniziale, la magia avviene all'interno della funzione riduttore: controlla il tipo di azione avvenuta e, in base a tale azione, il riduttore determinerà quali aggiornamenti applicare allo stato e restituirà il suo nuovo valore .

Esaminando il codice seguente, la funzione riduttore ha due possibili azioni:

  • "LOGIN": in tal caso lo stato dell'utente verrà aggiornato con le nuove credenziali utente fornite all'interno del payload dell'azione.

  • "LOGOUT": in tal caso lo stato dell'utente verrà rimosso dalla memoria locale e riportato a null.

È importante notare che l'oggetto azione contiene sia un campo tipo che determina quale logica applicare sia un campo payload opzionale per fornire i dati necessari per applicare tale logica.

Infine, l'hook useReducer restituisce lo stato corrente e una funzione di invio che usiamo per passare un'azione al riduttore.

Per il resto la logica è identica all'esempio precedente:

./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>
  );
};

Invio di azioni invece di utilizzare la funzione setter setState – ad esempio: setName –

Come abbiamo appena accennato, utilizziamo la funzione di invio per passare un'azione al riduttore, nel codice seguente avviamo un'azione LOGIN e forniamo l'e-mail dell'utente come carico utile, ora lo stato dell'utente verrà aggiornato e questa modifica si attiverà un re-rendering di tutti i componenti iscritti allo stato utente. È importante sottolineare che un nuovo rendering verrà attivato solo se si verifica un effettivo cambiamento nello stato, nessun nuovo rendering se il riduttore restituisce lo stesso stato precedente.

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 (...)};

Login utente

JWT Local storage

Suggerimento da professionista

Nota come l'oggetto utente che riceviamo dopo un accesso riuscito è ora archiviato in localStorage.

Hook personalizzato per l'accesso

Ora che abbiamo una buona conoscenza di useReducer, possiamo incapsulare ulteriormente la nostra logica di login e logout nei loro hook personalizzati separati, con una singola chiamata all'hook Login, possiamo gestire una chiamata API al percorso di login, recuperare il nuovo utente credenziali e memorizzarle nella memoria locale, inviare una chiamata LOGIN per aggiornare lo stato dell'utente, il tutto occupandosi della gestione degli errori:

./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 };
};

Nota: per gli utenti React più avanzati tra i lettori, potresti chiederti perché non abbiamo utilizzato la funzione di inizializzazione lenta di useReducer per recuperare le credenziali dell'utente, useReducer accetta un terzo parametro opzionale chiamato funzione init, questa funzione viene utilizzata nel caso dobbiamo applicare un po' di logica prima di poter ottenere il valore iniziale dello stato, il motivo per cui non abbiamo optato per questo è una semplice questione di separazione delle preoccupazioni, il codice in questo modo è più semplice da comprendere e di conseguenza più semplice da mantenere .

Pagina di login

Ecco come appare la parte superiore della nostra pagina di accesso dopo aver utilizzato l'hook useLogin() per estrarre la funzione di accesso e aver invocato la funzione di accesso con le credenziali inviate da un utente:

// ... ... //
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 (...)
// ... ... //

Stiamo anche disabilitando la funzione Invia quando l'utente invia il modulo:

<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>

E rendendo eventuali errori di autenticazione che riceviamo dal nostro backend:

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

Rendering Errors

Mantenimento dello stato utente

Forse ti starai chiedendo perché dobbiamo archiviare l'oggetto utente in localStorage, in poche parole, vogliamo mantenere l'utente connesso finché il token non è scaduto. L'uso di localStorage è un ottimo modo per archiviare bit di JSON, proprio come nel nostro esempio. Nota come lo stato viene cancellato se aggiorni la pagina dopo aver effettuato l'accesso. Questo può essere risolto facilmente utilizzando un hook useEffect per verificare se abbiamo un oggetto utente memorizzato in localStorage, se ce n'è uno, accediamo automaticamente all'utente:

// ... ... //
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>
  );
};

Hook personalizzati per il logout

La stessa cosa vale con l'hook Logout, qui stiamo inviando un'azione LOGOUT per rimuovere le credenziali dell'utente corrente sia dallo stato che dall'archiviazione locale:

./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 };
};

Disconnessione dell'utente

Per disconnettere un utente, aggiungiamo un evento clic al pulsante Disconnetti trovato in UserDropdown.jsx e gestiamolo di conseguenza:

./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

Protezione delle rotte di reazione

Il passaggio finale nell'implementazione della nostra applicazione è sfruttare lo stato utente globale per controllare la navigazione dell'utente, un rapido promemoria su quale comportamento dovremmo adottare: inizialmente l'utente viene accolto dalla pagina di accesso, da quel punto l'utente può accedere solo alla home page dopo un accesso riuscito, allo stesso modo l'utente verrà reindirizzato alla pagina di accesso dopo il logout.

Raggiungiamo questo obiettivo con l'aiuto della libreria react-router-dom definendo 2 percorsi: "/" e "/login", controlliamo quale componente visualizzare su ciascun percorso utilizzando lo stato di autenticazione globale, auth valutando su null rappresenta un utente non autenticato e viceversa:

./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;

Riassumendo il diagramma

Diagram

Incartare

In questo articolo abbiamo cercato di affrontare il caso d'uso semplice ma molto comune dell'implementazione della gestione dello stato per un flusso di lavoro di autenticazione, esaminando i diversi approcci, la logica alla base di ciascuno e i relativi compromessi. La gestione dello stato nei framework lato client e in React in particolare è uno degli argomenti più discussi nella comunità frontend, semplicemente perché può creare o distruggere le prestazioni e la scalabilità della tua applicazione. L'enorme quantità di diverse tecniche, modelli, librerie e strumenti che cercano di risolvere questo problema di gestione dello stato è travolgente, il nostro obiettivo era darti una solida comprensione delle pratiche in modo che tu possa implementarle nella tua applicazione, per applicazioni più avanzate tecniche e modelli di gestione dello stato in applicazioni più complesse, dai un'occhiata al nostro prossimo articolo in cui entreremo nel redux per la gestione dello stato scalabile in React.

Prossimamente

ContextAPI rispetto a Redux Toolkit

Redux è stato adottato da molti anni nella comunità prima del toolkit Redux, infatti, RTK è stato introdotto come modello iniziale per avviare la gestione dello stato Redux in nuove applicazioni (il suo nome iniziale era "redux-starter-kit" nell'ottobre 2019), sebbene oggi, c'è un consenso generale tra i manutentori di Redux e la comunità sul fatto che il toolkit Redux sia il modo valido per lavorare con redux.RTX astrae gran parte della logica di Redux che lo rende più facile da usare, con molto meno verboso, e incoraggia gli sviluppatori a seguire le migliori pratiche, una differenza principale tra i due è che Redux è stato costruito per non avere opinioni, fornendo un'API minima e aspettandosi che gli sviluppatori facciano la maggior parte del lavoro pesante scrivendo le proprie librerie per attività comuni e occupandosi della struttura del codice, ciò ha comportato tempi di sviluppo lenti e codice disordinato, il toolkit Redux è stato aggiunto come ulteriore livello di astrazione che impedisce agli sviluppatori di cadere nelle sue trappole comuni, fare riferimento alla documentazione ufficiale per ulteriori approfondimenti e il ragionamento dei suoi manutentori qui.


Career Services background pattern

Servizi per le carriere

Contact Section background image

Rimaniamo in contatto

Code Labs Academy © 2025 Tutti i diritti riservati.