
GUIDE PER ASPIRANTI PROGRAMMATORI
Quando evitare l’uso di Context in React
Quando si parla di Context, c’è un classico caso d’uso problematico che andrebbe evitato: vediamo quale e perché. Normalmente, l’utilizzo di Context si divide in tre passi: La chiamata a createContext. La creazione del Provider. Le chiamate a useContext. Due classici errori che si fanno quando si utilizza Context sono: Non passare…


Vuoi avviare una nuova carriera o fare un upgrade?
Trova il corso Digital & Tech più adatto a te nel nostro catalogo!
Quando si parla di Context, c’è un classico caso d’uso problematico che andrebbe evitato: vediamo quale e perché.
Normalmente, l’utilizzo di Context si divide in tre passi:
- La chiamata a createContext.
- La creazione del Provider.
- Le chiamate a useContext.
Due classici errori che si fanno quando si utilizza Context sono:
- Non passare un valore di default a createContext.
- Esportare l’istanza di Context.
Per esempio, viene creata l’istanza di Context in un file (senza valore di default):
import { createContext } from "react"; export const AuthContext = createContext();
E, poi, viene importata in un file separato per l’utilizzo del Provider:
import { AuthContext } from "./AuthContext"; export default function AuthContextProvider({ children }) { // ... return ( <AuthContext.Provider value={ { /* ... */ } } > {children} </AuthContext.Provider> ); }
Infine, vengono utilizzati Context e Provider importati dai due file diversi:
import { AuthContext } from "../contexts/AuthContext"; import { AuthContextProvider } from "../contexts/AuthContextProvider"; export default function SomeComponent() { const { isLoggedIn, login, logout } = useContext(AuthContext); return <AuthContextProvider>{/* ... */}</AuthContextProvider>; }
Questi due errori portano a due problemi:
- L’assenza di un valore predefinito passato createContext rende più difficile a chi vorrebbe usarlo capire che tipo di funzionalità sono previste. Se il Provider, poi, è definito in un file separato, la ricerca diventa ancora più frustrante.
- Esportare l’istanza di Context significa renderlo disponibile non solo per l’utilizzo da Consumer, ma anche per l’utilizzo da Provider. Chiunque potrebbe creare un secondo AuthContextProvider o, peggio ancora, utilizzare direttamente AuthContext.Provider in un componente, generando il caos.
Ecco perché non abbiamo esportato AuthContext. Non avendolo esportato, è impossibile utilizzare questa sintassi in un componente:
const { isLoggedIn, login, logout } = useContext(AuthContext); // ^^^^^^^^^^^ // irraggiungibile! Ecco perché esportiamo una funzione hook apposita, useAuthContext: export function useAuthContext() { return useContext(AuthContext); }
Come ormai sai bene, le funzioni hook sono funzioni che hanno un nome che inizia per use e possono usare altre funzioni hook. useAuthContext ha un nome che inizia per use e, di conseguenza, può utilizzare la funzione hook useContext fornita da React.
A questo punto, non ci resta che fare due cose:
- Inserire il componente AuthContextProvider.
- Utilizzare useAuthContext.
Useremo useAuthContext in App, perché è lì che gestiamo l’autenticazione. Di conseguenza, AuthContextProvider dovrà essere un componente genitore di App, perché possiamo usare un Context solo se il componente che lo usa è figlio del Provider di quel Context. L’unico punto in cui possiamo inserire componenti genitori di App è index.js (o index.jsx, se hai usato Vite).
// File: src/index.js(x) // ... import { AuthContextProvider } from "./contexts/AuthContext"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <AuthContextProvider> <App /> </AuthContextProvider> </React.StrictMode> ); // ...
Ora possiamo usare useAuthContext in App:
// File: src/App.jsx // ... import { useAuthContext } from "./contexts/AuthContext"; // ... export default function App() { const { isLoggedIn, login } = useAuthContext(); // ... if (isLoggedIn()) { return <div className="App">{/* ... */}</div>; } else { return <LoginForm onLogin={login} />; } }
Ora, se volessimo inserire un bottone “Logout” in un componente, ci basterebbe scrivere una cosa del genere:
export default function AnyComponent() { const { logout } = useAuthContext(); return ( {/* ... */} <button onClick={logout}>Logout</button> {/* ... */} ) }
Il fatto di non aver esportato AuthContext ci permette di legarlo al concetto di autenticazione dell’utente, esponendo solo le funzionalità che servono. Come nel caso delle funzioni hook, Context è un modo per isolare funzionalità e concetti. AuthContext gestisce internamente il flusso di autenticazione attraverso una macchina a stati finiti ed espone la possibilità di leggerne lo stato e scatenarne le transizioni.
I pericoli dell’uso di Context in React
L’utilizzo di Context, come abbiamo visto, è estremamente comodo per evitare il problema del prop drilling. Come sempre, però, essendo una cosa che esula dal normale comportamento di React (prop, eventi e stati) è uno strumento da usare con moderazione.
Un utilizzo eccessivo di Context – per esempio usandolo anche solo per evitare il passaggio di prop da un componente al foglio del figlio – potrebbe portare a questi problemi:
- Eccessiva frammentazione della logica: Context andrebbe usato per isolare concetti indipendenti, come l’autenticazione dell’utente, il tema grafico dell’applicazione o una configurazione globale. Usarlo solo come strumento per evitare il prop drilling potrebbe distrarti dal suo vero scopo e portarti a creare Context legati a pezzi di stato dell’applicazione, come una pagina specifica, un form, una chiamata di rete.
- Diminuzione della riusabilità dei componenti: i Provider sono dei componenti controller che controllano componenti view su più livelli, condividendone lo stato. Questo significa che ogni componente Consumer, oltre a essere legato alle prop, è legato anche al Provider. Se volessimo spostare un componente del genere da un’applicazione a un’altra, oltre a collegarne le prop, dovremmo anche ricreare le istanze Context corrispondenti, oppure modificare il componente per smettere di utilizzare le istanze di Context.
- Problemi di Performance: sappiamo che, ogni volta che un componente cambia stato, React chiama tutti i suoi discendenti al rendering. I Provider sono normalmente utilizzati a un livello molto vicino alla radice dell’applicazione (il nostro componente App) o, come nel nostro caso, a un livello ancora più estero (index.js(x)). Di conseguenza, il cambio dello stato di un’istanza di Context scatena normalmente grandi cicli di rendering, mettendo a dura prova le performance di React.
Occhio, quindi, a non esagerare!
CONTENUTI GRATUITI IN EVIDENZA
Guide per aspiranti programmatori 👨🏻🚀
Vuoi muovere i primi passi nel Digital e Tech? Abbiamo preparato alcune guide per aiutarti a orientarti negli ambiti più richiesti oggi.