Ogni sviluppatore React, prima o poi, si scontra con lo stesso ostacolo. Recuperi un oggetto user all'interno del tuo componente App di alto livello. Poi lo passi verso il basso. E ancora più giù. Attraverso un wrapper di routing, attraverso uno shell di layout, attraverso un contenitore della sidebar, solo perché un minuscolo componente avatar a tre livelli di profondità possa visualizzare una foto del profilo. I componenti intermedi non si curano di quell'oggetto user. Si limitano a inoltrare il pacco. Questo è il prop drilling, e trasforma un albero di componenti pulito in un frustrante gioco del telefono.

Il vero problema inizia quando la struttura di quei dati cambia. Magari il backend inizia a annidare user.profile.avatar invece di user.avatar. All'improvviso ti ritrovi a modificare interfacce TypeScript o PropTypes in cinque file diversi che non utilizzano mai direttamente quei dati. È qui che entra in gioco la React Context API.

Come il Context riconfigura il flusso dei dati

Pensa al Context come a un router WiFi situato al centro della tua casa. Senza di esso, avresti bisogno di cavi Ethernet che si snodano in ogni stanza per portare il segnale al tuo laptop. Con esso, il router trasmette nell'aria e qualsiasi dispositivo con la password corretta può connettersi direttamente. Le pareti non contano.

In termini React, la radice della tua app può trasmettere dati attraverso l'albero dei componenti senza chiedere a ogni livello di agire come un corriere. Qualsiasi componente annidato può iscriversi a quella trasmissione e ricevere esattamente ciò di cui ha bisogno.

I tre elementi fondamentali

La Context API si riduce a tre parti mobili.

React.createContext() configura il canale di trasmissione. Restituisce un oggetto contenente un Provider e (nel codice più vecchio) un Consumer. Devi chiamarlo una sola volta per una determinata funzionalità.

Il Provider è un componente che avvolge una sezione del tuo albero. Accetta una prop chiamata value. Tutto ciò che inserisci in quella prop diventa disponibile per ogni discendente, non importa quanto sia profondo.

useContext è l'Hook che permette a un function component di intercettare quella trasmissione. All'interno del tuo componente, passi l'oggetto context che hai creato a useContext, e questo restituisce il valore corrente. Tutto qui. Niente wrapper, niente prop extra.

Prima dell'arrivo degli Hook, era necessario usare il pattern Consumer con le render props. Funzionava, ma creava molto disordine dovuto all'indentazione e ai wrapper. useContext ha appiattito tutto questo in una singola riga all'interno del corpo della funzione.

Quando il Context ha davvero senso

Non ricorrere al Context per abitudine. È progettato per dati che molti componenti non correlati condividono attraverso diversi rami del tuo albero. I candidati ideali includono:

  • Impostazioni del tema. Non solo la modalità chiara o scura, ma anche token di spaziatura, palette di colori e scale dei font. Gestire manualmente questi valori attraverso ogni pulsante stilizzato e ogni modal diventa noioso molto velocemente.
  • Autenticazione dell'utente. Stato del login, un array di permessi o l'oggetto utente corrente. La tua barra dell'header, un widget della dashboard e una guardia per le rotte private potrebbero trovarsi in angoli diversi dell'albero.
  • Preferenze della lingua. Stringhe di localizzazione, formati delle date e simboli di valuta. I componenti foglia profondi, come le etichette dei form, ne hanno bisogno senza che ogni genitore sul percorso debba conoscerli.
  • Dati del carrello. Conteggio degli articoli, valore totale e funzioni per aggiungere al carrello. Il badge dell'header e la pagina di checkout hanno bisogno dello stesso stato, ma solitamente si trovano sotto rami di layout completamente diversi.

Un selettore di tema pratico

Uno dei modi più chiari per vedere il Context in azione è un toggle del tema. Ecco come potresti implementarlo senza saltare i dettagli che contano davvero.

Per prima cosa, crea un file ThemeContext.js. Chiama React.createContext() e memorizza il risultato. Poi costruisci un componente ThemeProvider che gestisce il tema corrente con useState o useReducer. Avvolgi i children nel Provider del tuo context, passando un oggetto che contenga sia il tema corrente che una funzione per alternarlo. Esporta sia il ThemeProvider che l'oggetto context stesso.

In secondo luogo, vai al punto di ingresso della tua app. Importa ThemeProvider e avvolgi l'intera applicazione con esso. Se salti questo passaggio, qualsiasi cosa cerchi di leggere il context in seguito vedrà solo il valore predefinito.

In terzo luogo, all'interno di un componente Header o Content, importa l'oggetto context e useContext. Chiama l'Hook, destruttura il tema e la funzione di toggle, e applica le tue classi CSS condizionalmente. Aggiungi un pulsante che chiama il toggle. Il componente non riceve mai una prop theme dal suo genitore. Intercetta il segnale direttamente dall'aria.

Prop Drilling, Context o Redux?

Scegliere tra questi strumenti non riguarda tanto la fedeltà quanto la forma del proprio stato.

Il prop drilling va benissimo per due o tre livelli di profondità. È esplicito, facile da tracciare nel proprio IDE e mantiene le dipendenze evidenti. I problemi emergono solo quando si inizia a far passare lo stesso prop attraverso sei o sette livelli.

La Context API è inclusa in React stesso. Ciò significa nessun aumento della dimensione del bundle e nessuna configurazione esterna. Gestisce magnificamente stati globali di piccole e medie dimensioni, specialmente dati che cambiano di rado come temi o profili utente.

Redux richiede l'installazione di librerie aggiuntive e la scrittura di boilerplate. Ne vale la pena quando la logica dello stato è complessa, quando diverse slice di stato interagiscono in modo profondo o quando sono necessari middleware e il time-travel debugging. Per dati globali semplici, Redux è eccessivo.

La realtà sulle prestazioni di cui nessuno parla

Ecco l'inghippo che distingue le implementazioni junior da quelle senior. Quando il valore di un Context Provider cambia, ogni componente che consuma quel contesto viene renderizzato nuovamente. Non importa se la specifica slice di cui quel componente si occupa è rimasta invariata. React rileva il nuovo riferimento e pianifica un aggiornamento.

Se riversi l'intero stato dell'applicazione in un unico, enorme StoreContext, avrai di fatto incollato tutta la tua UI. Cambiare un'impostazione del tema farà renderizzare nuovamente il carrello, i grafici della dashboard e la lista delle notifiche. È un lavoro inutile.

Dividi i tuoi contesti per dominio. Mantieni un ThemeContext per le impostazioni visive, un UserContext per i dati del profilo e un CartContext per lo stato del commercio. Se un utente modifica il proprio nome visualizzato, l'header si aggiornerà senza toccare la griglia dei prodotti. Inoltre, fai attenzione a ciò che passi nella prop value del Provider. Se passi un oggetto letterale { theme, toggleTheme } inline durante il render, creerai un nuovo riferimento a ogni render, innescando aggiornamenti non necessari. Stabilizza quella struttura con useMemo se il valore contiene funzioni o dati non primitivi.

Errori che fanno perdere ore

Due errori colpiscono i team ripetutamente.

Dimenticare di esportare l'oggetto context. È facile esportare il componente ThemeProvider e poi provare a chiamare useContext(ThemeProvider). Non funziona così. L'Hook ha bisogno dell'oggetto context restituito da createContext, non del componente wrapper. Se esporti solo il Provider, i tuoi consumatori non avranno nulla da importare.

Chiamare useContext al di fuori del suo Provider. L'Hook restituisce il valore predefinito passato a createContext. Se non hai passato un valore predefinito, otterrai undefined. Se l'albero dei componenti renderizza il consumatore più in alto nel DOM rispetto al Provider, o se il Provider è del tutto assente, i dati semplicemente non arriveranno. Controlla due volte che il tuo file index o root avvolga effettivamente l'app.

La vera lezione

React Context non è una rivoluzione nella gestione dello stato. È uno strumento mirato per un problema spaziale specifico: far arrivare i dati ai componenti distanti senza trasformare ogni livello in un ufficio postale. Usalo per dati realmente globali, mantieni i tuoi contesti divisi per dominio per proteggere le prestazioni di rendering e avvolgi sempre l'albero con il Provider corretto prima di provare a leggere il segnale. Adotta queste abitudini e i tuoi alberi di componenti rimarranno puliti, veloci e facili da comprendere.