Staatsbeheer in reactie: een praktisch voorbeeld

reageren
staatsbeheer
context-API
Staatsbeheer in reactie: een praktisch voorbeeld cover image

React is voor veel ontwikkelaars het go-to-framework voor het bouwen van dynamische client-side applicaties. Het dynamische karakter van deze applicaties komt voort uit de flexibiliteit en de uitgebreide lijst met mogelijkheden en functies die mogelijk zijn aan de clientzijde, waardoor ontwikkelaars volwaardige applicaties konden bouwen die binnen enkele seconden in de browser werden geladen, een prestatie die niet mogelijk was mogelijk (of zeer omslachtig) in de tijd van het statische web.

Met deze uitbreiding van de mogelijkheden ontstond het concept van het beheren van de status. Naarmate de complexiteit toeneemt in client-side applicaties, groeit de noodzaak om de lokale status te behouden uit tot een knelpunt op zichzelf als deze niet op de juiste manier wordt afgehandeld en wordt bedacht met schaalbaarheid in gedachten.

Dit probleem werd door veel raamwerken aangepakt, waarbij verschillende benaderingen werden gevolgd en de nadruk werd gelegd op verschillende reeksen subproblemen. Daarom is het belangrijk om een ​​goed begrip te hebben van het ecosysteem van het raamwerk van keuze om de behoeften van elke toepassing te beoordelen en de juiste aanpak te hanteren om die te volgen. statistieken. Dit artikel geeft u een kort overzicht van de veelvoorkomende problemen met staatsbeheer en probeert verschillende benaderingen (useState, Context API) te introduceren als reactie daarop. Hoewel dit artikel meerdere oplossingen zal presenteren, zal het zich alleen richten op uitdagingen op kleinere schaal. In de komende artikelen zullen we meer geavanceerde onderwerpen behandelen.

Authenticatieworkflow

De code die in het hele artikel wordt getoond, kun je hier. vinden.

Een link naar een livevoorbeeld vindt u hier.

Neem het geval waarin we het authenticatieproces voor een React-applicatie implementeren.

User Login

Zoals weergegeven in de GIF hierboven, willen we gebruikers toestaan ​​in te loggen of zich aan te melden bij onze applicatie met behulp van inloggegevens. Als er geldige inloggegevens zijn opgegeven, wordt de gebruiker ingelogd, navigeert de applicatie automatisch naar de startpagina en kan de gebruiker doorgaan met het gebruiken van de applicatie.

Op dezelfde manier worden, als de gebruiker uitlogt, de bronnen op de startpagina beschermd achter inloggen; de inlogpagina is de enige pagina die toegankelijk is voor de gebruiker.

Als we deze workflow in termen van implementatie beschouwen, zouden we een hoofdcomponent hebben met de naam App, de app-component zal de gebruiker doorsturen naar een van de twee pagina's: Home of Login, de huidige status van de gebruiker (ingelogd, uitgelogd) zal bepalen welke pagina waarnaar de gebruiker wordt doorgestuurd, zou een verandering in de huidige status van de gebruiker (bijvoorbeeld het veranderen van ingelogd naar uitgelogd) een onmiddellijke omleiding naar de overeenkomstige pagina moeten veroorzaken.

State

Zoals u in de bovenstaande afbeelding kunt zien, willen we dat de app-component rekening houdt met de huidige status en slechts één van de twee pagina's – Home of Login – weergeeft op basis van die huidige status.

Als de gebruiker nul is, betekent dit dat we geen geverifieerde gebruiker hebben. Daarom navigeren we automatisch naar de inlogpagina en beschermen we de startpagina met voorwaardelijke weergave. Als de gebruiker bestaat, doen we het tegenovergestelde.

Nu we een goed begrip hebben van wat er moet worden geïmplementeerd, laten we een paar opties verkennen, maar laten we eerst ons React-project opzetten.

De projectrepository bevat een voorbeeld van een backend-applicatie die we zullen gebruiken om de frontend-kant te implementeren (we zullen er niet op ingaan omdat dit hier niet de belangrijkste focus is, maar de code is met opzet eenvoudig gehouden, zodat je er geen moeite mee zult hebben )

We beginnen met het maken van de volgende pagina's en componenten:

  • Startpagina

  • Login pagina

  • Bewerk gebruikerspagina

  • Navbar-component

  • UserDropdown-component

Een React-applicatie met meerdere pagina's heeft een goede navigatie nodig, daarvoor kunnen we de react-router-dom gebruiken om een ​​globale browserroutercontext te creëren en verschillende React-routes te registreren.


yarn add react-router-dom

We weten dat veel lezers de voorkeur geven aan het volgen van tutorials, dus hier is het startersjabloon om u op weg te helpen. Deze startertak gebruikt DaisyUI voor vooraf gedefinieerde TailwindCSS JSX-componenten. Het bevat alle componenten, pagina's en de router die al is ingesteld. Als je overweegt om deze tutorial te volgen en de hele authenticatiestroom zelf op te bouwen door eenvoudige stappen te volgen, begin dan eerst met het forken van de repository. Nadat je de repository hebt geforkt, kloon je deze en begin je vanaf de start-herebranch:


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

Zodra u de start-hier-tak trekt:

  • Open het project met de code-editor van uw voorkeur

  • Wijzig de map naar frontend/

  • Installeer afhankelijkheden: garen

  • Start een ontwikkelingsserver: garen dev·

Het voorbeeld zou er ongeveer zo uit moeten zien:

Changing user name

De naam die in de navigatiebalk wordt weergegeven (rechtsboven) is een statusvariabele die is gedefinieerd in het hoofdapp-onderdeel. Dezelfde variabele wordt doorgegeven aan zowel de navigatiebalk als de startpagina. Het eenvoudige formulier dat hierboven wordt gebruikt, werkt feitelijk de statusvariabele "naam" bij vanuit de EditPage-component.

De twee hieronder gepresenteerde benaderingen gaan in op de details van de implementatie:

Eerste benadering: useState

gebruikState()

useState is een van de meest gebruikte React-hooks. Hiermee kunt u de status in een React Functional-component creëren en muteren. De useState-component heeft een heel eenvoudige implementatie en is gemakkelijk te gebruiken: om een ​​nieuwe status te creëren, moet je useState aanroepen met de initiële waarde van je staat en de useState-hook retourneert een array met twee variabelen: de eerste is de staat variabele die u kunt gebruiken om naar uw staat te verwijzen, en de tweede een functie die u gebruikt om de waarde van de staat te wijzigen: vrij eenvoudig.

Zullen we dat in actie zien? De naam die in de navigatiebalk wordt weergegeven (rechtsboven) is een statusvariabele die is gedefinieerd in het hoofdapp-onderdeel. Dezelfde variabele wordt doorgegeven aan zowel de navigatiebalk als de startpagina. Het eenvoudige formulier dat hierboven wordt gebruikt, werkt feitelijk de statusvariabele "naam" bij vanuit de EditPage-component. Het komt hierop neer: useState is een kernhaak die een initiële status als parameter accepteert en twee variabelen retourneert die twee waarden bevatten, waarbij de statusvariabele de initiële status bevat, en een setterfunctie voor dezelfde statusvariabele.

Laten we het opsplitsen en kijken hoe dat in de eerste plaats werd geïmplementeerd.

  1. De statusvariabele “naam” maken:

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

Rekwisieten

Props zijn een van de basisbouwstenen van een react-component. Conceptueel gezien, als je een functionele React-component als een Javascript-functie beschouwt, dan zijn props niet meer dan de functieparameters. Het combineren van props en de useState-hook kan je een solide raamwerk bieden voor het beheren van de status in een eenvoudige React-applicatie.

React-props worden als attributen doorgegeven aan aangepaste componenten. Attributen die als rekwisieten worden doorgegeven, kunnen worden gedestructureerd uit het props-object wanneer ze als argument worden geaccepteerd, vergelijkbaar met dit:

Rekwisieten doorgeven

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

Props kunnen worden geaccepteerd en gebruikt binnen een functionele component, vergelijkbaar met normale functieargumenten. Omdat “naam” als prop wordt doorgegeven aan de Home-component, kunnen we deze in dezelfde component weergeven. In het volgende voorbeeld accepteren we de doorgegeven prop met behulp van de destructurerende syntaxis om de eigenschap name uit het props-object te extraheren.

Rekwisieten accepteren

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

Pro-tip

Open de console van de browser en merk op hoe alle componenten die de prop "naam" gebruiken, opnieuw worden weergegeven wanneer de status verandert. Bij het manipuleren van een statusvariabele slaat React de volgende status op, geeft uw component opnieuw weer met de nieuwe waarden en werkt de gebruikersinterface bij.

Components Re-rendering

useState Nadelen

Rekwisieten-boren

Props-boren is een term die verwijst naar een hiërarchie van componenten waarbij een reeks componenten bepaalde rekwisieten nodig heeft die door een bovenliggende component worden geleverd. Een veel voorkomende oplossing die onervaren ontwikkelaars gewoonlijk gebruiken, is om deze rekwisieten door de hele keten van componenten te laten gaan. Het probleem hiermee De benadering is dat een verandering in een van deze rekwisieten ervoor zal zorgen dat de hele keten van componenten opnieuw wordt weergegeven, waardoor de hele applicatie effectief wordt vertraagd als gevolg van deze onnodige weergaven, de componenten in het midden van de keten die deze rekwisieten niet nodig hebben fungeren als medium voor het overbrengen van rekwisieten.

Voorbeeld:

  • Er is een statusvariabele gedefinieerd in de hoofdcomponent van de app met behulp van de hook useState().

  • Er is een naamprop doorgegeven aan de Navbar-component

  • Dezelfde prop geaccepteerd in de navigatiebalk en nogmaals doorgegeven als prop aan de UserDropdown-component

  • UserDropdown is het laatste onderliggende element dat de prop accepteert.

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

Stel je voor hoe moeilijk het is om een ​​React-app te onderhouden met verschillende lagen componenten die allemaal dezelfde status doorgeven en weergeven.

Toenemende complexiteit en codekwaliteit

Met het gebruik van useState en rekwisieten als het enige middel voor statusbeheer in een react-applicatie, kan de codebase snel in complexiteit groeien, waarbij tientallen of honderden statusvariabelen moeten worden beheerd, die duplicaten van elkaar kunnen zijn, verspreid over verschillende bestanden en Componenten kunnen behoorlijk intimiderend zijn; elke wijziging aan een bepaalde statusvariabele vereist een zorgvuldige afweging van de afhankelijkheden tussen componenten om mogelijke extra weergave in een toch al trage applicatie te voorkomen.

Tweede aanpak: Context-API

De Context API is de poging van React om de nadelen op te lossen van het alleen gebruiken van rekwisieten en useState voor statusbeheer. In het bijzonder komt de context-API als antwoord op het eerder genoemde probleem met betrekking tot de noodzaak om rekwisieten door te geven aan de gehele componentenboom. Met behulp van context kunt u een status definiëren voor gegevens die u als globaal beschouwt en vanaf elk punt in de componentenboom toegang krijgen tot de status: geen prop-driving meer.

Het is belangrijk om erop te wijzen dat de context-API oorspronkelijk is ontworpen om het probleem van het wereldwijd delen van gegevens op te lossen (gegevens zoals UI-thema's, authenticatie-informatie die onze gebruikssituatie is, talen en dergelijke), voor andere soorten gegevens die moeten worden gedeeld tussen meer dan één component, maar is niet noodzakelijkerwijs globaal voor de hele applicatie. Context is misschien niet de beste optie. Afhankelijk van de gebruikscasus kunt u andere technieken overwegen, zoals componentsamenstelling, wat buiten het bestek van dit artikel valt.

Overschakelen naar context

De authenticatiecontext creëren

createContext is eenvoudig, het creëert een nieuwe contextvariabele en bevat een enkele optionele parameter: de standaardwaarde van de contextvariabele.

Laten we dit in actie zien, maak eerst een nieuwe map “contexts” en een nieuw bestand daarin “Auth.jsx”. Om een ​​nieuwe context te creëren, moeten we de functie createContext() aanroepen en de geretourneerde waarde toewijzen aan een nieuwe variabele Auth die vervolgens wordt geëxporteerd:

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

Geef de verificatiecontext op

Nu moeten we de contextvariabele "Auth" blootleggen die we eerder hebben gemaakt. Om dit te bereiken gebruiken we de contextprovider, de contextprovider is een component die een "waarde"-prop heeft, we kunnen deze prop gebruiken om een ​​waarde te delen: naam – door de componentenboom heen in de componentenboom te verpakken met de providercomponent, maken we die waarde overal in de componentenboom toegankelijk, zonder de noodzaak om die prop aan elke onderliggende component afzonderlijk door te geven.

In ons authenticatievoorbeeld hebben we een variabele nodig om het gebruikersobject vast te houden, en een soort setter om die statusvariabele te manipuleren. Dit is een perfecte use-case voor useState. Wanneer u Context gebruikt, moet u ervoor zorgen dat u de gegevens definieert die u wilt verstrekken, en die gegevens (gebruiker in ons voorbeeld) doorgeeft aan alle componentenboom die erin is genest, dus definieer een nieuwe statusvariabele-gebruiker binnen de providercomponent en ten slotte geven we zowel user als setUser binnen een array door als de waarde die de provider zal blootstellen aan de componentenboom:

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

Een ander ding dat we kunnen doen is onze statusvariabele “naam” verplaatsen van de hoofdcomponent van de app naar de Auth-context en deze blootstellen aan geneste componenten:

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

Het enige dat nu nog overblijft is het nesten van onze app in dezelfde AuthProvider-component die we zojuist hebben geëxporteerd.

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

Omdat we de onderliggende prop binnen Auth.Provider weergeven, kunnen alle elementen die in de component AuthProvider zijn genest nu de waardeprop gebruiken die we aan Auth.Provider hebben doorgegeven. Het lijkt misschien verwarrend, maar als je er eenmaal mee experimenteert, probeer dan een mondiale contexttoestand te bieden en te consumeren. Het werd tenslotte pas logisch voor mij nadat ik met de Context API had geëxperimenteerd.

De authenticatiecontext gebruiken

De laatste stap is eenvoudig: we gebruiken de context hook "useContext" om toegang te krijgen tot de waarde die de contextprovider van "Auth" levert, wat in ons geval de array is die user en setUser bevat. In de volgende code kunnen we dat doen gebruik useContext om de Auth-context in de navigatiebalk te gebruiken. Dit is alleen mogelijk omdat Navbar is genest in de App-component, en aangezien AuthProvider zich om de App-component heen wikkelt, kan de waardeprop alleen worden gebruikt met behulp van de useContext-hook. Nog een geweldige tool die React kant-en-klaar biedt om alle gegevens te beheren die wereldwijd toegankelijk zijn en die ook door elk consumentencomponent kunnen worden gemanipuleerd.

./src/components/Navbar.jsx

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

 return (...)};

Merk op dat we geen rekwisieten meer accepteren in de functionele component Navbar(). We gebruiken useContext(Auth) om in plaats daarvan de Auth-context te gebruiken, waarbij we zowel naam als setName pakken. Dit betekent ook dat we de prop niet meer aan Navbar hoeven door te geven:

./src/App.jsx

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

De authenticatiecontext bijwerken

We kunnen ook de meegeleverde setName-functie gebruiken om de statusvariabele “name” te manipuleren:

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

Introductie van useReducer() hook

In het vorige voorbeeld hebben we de context-API gebruikt om onze status in de componentenboom te beheren en te delen. Het is je misschien opgevallen dat we nog steeds useState gebruiken als de basis van onze logica, en dit is meestal oké omdat onze status nog steeds een eenvoudig object is op dit punt, maar als we de mogelijkheden van onze authenticatiestroom zouden uitbreiden, zullen we zeker meer moeten opslaan dan alleen het e-mailadres van de gebruiker die momenteel is ingelogd, en dit is waar we terugkeren naar de beperkingen waar we eerder op ingingen met betrekking tot het gebruik van useState met een complexe status, gelukkig lost React dit probleem op door een alternatief te bieden voor useState voor het beheren van een complexe status: voer useReducer in.

useReducer kan worden gezien als een algemene versie van useState. Er zijn twee parameters voor nodig: een reducerfunctie en een initiële status.

Niets interessants om op te merken voor de initiële status, de magie gebeurt binnen de reducer-functie: het controleert op het type actie dat heeft plaatsgevonden, en afhankelijk van die actie zal de reducer bepalen welke updates op de staat moeten worden toegepast en de nieuwe waarde moeten retourneren .

Als we naar de onderstaande code kijken, heeft de reducerfunctie twee mogelijke acties:

  • "LOGIN": in welk geval de gebruikersstatus wordt bijgewerkt met de nieuwe gebruikersreferenties die zijn opgegeven in de actiepayload.

  • "LOGOUT": in welk geval de gebruikersstatus uit de lokale opslag wordt verwijderd en teruggezet op nul.

Het is belangrijk op te merken dat het actieobject zowel een typeveld bevat dat bepaalt welke logica moet worden toegepast, als een optioneel payloadveld om gegevens te leveren die nodig zijn voor het toepassen van die logica.

Ten slotte retourneert de hook useReducer de huidige status en een verzendfunctie die we gebruiken om een ​​actie door te geven aan de reducer.

Voor de rest van de logica is deze identiek aan het vorige voorbeeld:

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

Verzendacties in plaats van de setState setter-functie te gebruiken – bijvoorbeeld: setName –

Zoals we zojuist vermeldden, gebruiken we de verzendfunctie om een ​​actie door te geven aan de reducer. In de volgende code initiëren we een LOGIN-actie en geven we het e-mailadres van de gebruiker als payload, nu wordt de gebruikersstatus bijgewerkt en wordt deze wijziging geactiveerd een nieuwe weergave van alle componenten die zijn geabonneerd op de gebruikersstatus. Het is belangrijk om erop te wijzen dat een nieuwe weergave alleen wordt geactiveerd als er daadwerkelijk een verandering in de status optreedt, en geen nieuwe weergave als de reducer dezelfde vorige status retourneert.

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

Gebruiker login

JWT Local storage

Pro-tip

Merk op hoe het gebruikersobject dat we ontvangen na een succesvolle aanmelding nu wordt opgeslagen in de localStorage.

Aangepaste haak voor inloggen

Nu we een goede grip hebben op useReducer, kunnen we onze login- en uitloglogica verder inkapselen in hun eigen afzonderlijke aangepaste hooks. Door een enkele aanroep naar de Login hook kunnen we een API-aanroep naar de loginroute afhandelen en de nieuwe gebruiker ophalen inloggegevens en sla deze op in de lokale opslag, verzend een LOGIN-oproep om de gebruikersstatus bij te werken, terwijl u tegelijkertijd de foutafhandeling afhandelt:

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

Opmerking: voor meer gevorderde React-gebruikers onder de lezers, vraagt ​​u zich misschien af ​​waarom we de luie initialisatiefunctie van useReducer niet hebben gebruikt om de gebruikersreferenties op te halen, useReducer accepteert een derde optionele parameter genaamd een init-functie, deze functie wordt gebruikt in het geval we moeten wat logica toepassen voordat we de initiële waarde van de staat kunnen krijgen, de reden dat we hier niet voor hebben gekozen is een simpele kwestie van scheiding van zorgen, de code op deze manier is eenvoudiger te begrijpen en als gevolg daarvan eenvoudiger te onderhouden .

Login pagina

Hier ziet u hoe het bovenste deel van onze inlogpagina eruit ziet nadat u de hook useLogin() hebt gebruikt om de inlogfunctie te extraheren en de inlogfunctie hebt aangeroepen met inloggegevens die door een gebruiker zijn ingediend:

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

We schakelen ook de functie Verzenden uit wanneer de gebruiker het formulier indient:

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

En eventuele authenticatiefouten weergeven die we van onze backend ontvangen:

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

Rendering Errors

De gebruikersstatus behouden

U vraagt ​​zich misschien af ​​waarom we het gebruikersobject in de localStorage moeten opslaan. Simpel gezegd: we willen de gebruiker ingelogd houden zolang het token niet is verlopen. Het gebruik van localStorage is een geweldige manier om stukjes JSON op te slaan, net als in ons voorbeeld. Merk op hoe de status wordt weggevaagd als je de pagina ververst nadat je hebt ingelogd. Dit kan eenvoudig worden opgelost met behulp van een useEffect hook om te controleren of we een opgeslagen gebruikersobject in localStorage hebben. Als die er is, loggen we de gebruiker automatisch in:

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

Aangepaste haken voor uitloggen

Hetzelfde geldt voor de Logout-hook, hier verzenden we een LOGOUT-actie om de huidige gebruikersreferenties uit zowel de staat als de lokale opslag te verwijderen:

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

Gebruiker uitloggen

Om een ​​gebruiker uit te loggen, voegen we een klikgebeurtenis toe aan de knop Uitloggen in UserDropdown.jsx, en behandelen we deze dienovereenkomstig:

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

React-routes beschermen

De laatste stap bij het implementeren van onze applicatie is het benutten van de globale gebruikersstatus om de gebruikersnavigatie te controleren, een snelle herinnering over welk gedrag we moeten bereiken: in eerste instantie wordt de gebruiker begroet door de inlogpagina, vanaf dat moment heeft de gebruiker alleen toegang tot de startpagina na een succesvolle aanmelding wordt de gebruiker bij het uitloggen op dezelfde manier doorgestuurd naar de inlogpagina.

We bereiken dit met behulp van de react-router-dom-bibliotheek door 2 routes te definiëren: "/" en "/login", we bepalen welk onderdeel op elke route moet worden weergegeven met behulp van de globale auth-status, representeert auth die naar null evalueert een niet-geauthenticeerde gebruiker en omgekeerd:

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

Samenvatting van het diagram

Diagram

Afronden

In dit artikel hebben we geprobeerd het eenvoudige maar veel voorkomende gebruiksscenario van het implementeren van statusbeheer voor een authenticatieworkflow aan te pakken, waarbij we de verschillende benaderingen, de grondgedachte achter elke benadering en hun afwegingen doornemen. Statusbeheer in client-side frameworks en in het bijzonder in React is een van de meest besproken onderwerpen in de frontend-gemeenschap, simpelweg omdat het de prestaties en de schaalbaarheid van uw applicatie kan maken of breken. De enorme hoeveelheid verschillende technieken, patronen, bibliotheken en hulpmiddelen die dit probleem van staatsbeheer proberen op te lossen, is overweldigend. Ons doel was om u een gedegen inzicht te geven in de praktijken, zodat u deze in uw eigen toepassing kunt implementeren, voor meer gevorderden. technieken en patronen van statusbeheer in complexere toepassingen, bekijk ons ​​volgende artikel waarin we ingaan op redux voor schaalbaar statusbeheer in React.

Binnenkort beschikbaar

ContextAPI versus Redux Toolkit

Redux werd al vele jaren in de gemeenschap geaccepteerd voordat de Redux-toolkit werd geïntroduceerd. RTK werd feitelijk geïntroduceerd als een startersjabloon om redux-statusbeheer in nieuwe applicaties op te starten (de oorspronkelijke naam was "redux-starter-kit" in oktober 2019), hoewel Tegenwoordig bestaat er een algemene consensus tussen de Redux-onderhouders en de gemeenschap dat de Redux-toolkit de geldige manier is om met redux te werken. RTX abstraheert veel van de Redux-logica, waardoor het gemakkelijker te gebruiken is, met veel minder uitgebreidheid, en moedigt ontwikkelaars aan om volg de best practices, een belangrijk verschil tussen de twee is dat Redux is gebouwd om geen mening te hebben, een minimale API te bieden en van ontwikkelaars te verwachten dat ze het meeste zware werk doen door hun eigen bibliotheken te schrijven voor algemene taken en om te gaan met de codestructuur, dit resulteerde in een langzame ontwikkeltijd en rommelige code. Redux-toolkit werd toegevoegd als een extra abstractielaag waardoor ontwikkelaars niet in de gebruikelijke valkuilen terechtkwamen. Raadpleeg de officiële documentatie voor meer inzichten en de redenering van de beheerders hier.


Career Services background pattern

Carrièrediensten

Contact Section background image

Laten we in contact blijven

Code Labs Academy © 2025 Alle rechten voorbehouden.