useContext legge un valore fornito da un componente genitore, così un componente in profondità nell'albero può riceverlo senza che ogni componente intermedio lo passi come prop. Crei un context con createContext, racchiudi una parte dell'albero in un provider con un value, e chiami useContext(TheContext) ovunque ti serva quel valore.
Toolbar non nomina mai il tema, eppure entrambi i pulsanti cambiano quando spunti la casella. Questo è tutto lo scopo del context: i componenti in mezzo non trasportano una prop che non usano, che è il problema del prop drilling che il context risolve.
I tre passaggi
// 1. Create it once, outside any component, usually in its own file
export const ThemeContext = createContext('light');
// 2. Provide a value to a part of the tree
<ThemeContext value={theme}>
<Page />
</ThemeContext>
// 3. Read it in any component inside that part
const theme = useContext(ThemeContext);
In React 19 l'oggetto context stesso è il provider. Il codice più vecchio scrive <ThemeContext.Provider value={theme}>, che funziona ancora, quindi vedrai entrambe le forme.
useContext cerca risalendo l'albero il provider più vicino di quel context. Un componente legge quello più vicino, quindi puoi annidare i provider per sovrascrivere un valore in una sezione.
Il valore predefinito
L'argomento di createContext è ciò che useContext restituisce quando nessun provider sta sopra il componente. Non cambia quando cambia lo stato; è un ripiego.
Il primo Greeting non ha alcun provider sopra di sé e riceve il valore predefinito 'en'. Il secondo legge 'es', e il terzo legge il più vicino 'ja'. Cambia il value="es" esterno in value="ja" e cambia solo la seconda riga.
Aggiornare il context da un figlio
Il context passa solo un valore verso il basso. Per permettere a un figlio in profondità di cambiarlo, tieni il valore nello stato dove si trova il provider, e metti il setter nel context accanto a esso.
LoginButton chiama setUser, lo stato in App cambia, il provider riceve un nuovo valore, e anche Header si aggiorna. Nessuno dei due componenti ha ricevuto una prop.
Ogni consumer esegue un nuovo render quando il valore cambia
Quando il value di un provider cambia, React renderizza ogni componente che chiama useContext per quel context, anche se quel componente sta dentro un memo e le sue props sono rimaste uguali. React confronta il vecchio e il nuovo valore con Object.is. Quindi value={{ user, setUser }} è un nuovo oggetto a ogni rendering di App, e ogni consumer esegue un nuovo render anche quando user non è cambiato.
Entrambi i lettori registrano un log una volta al primo rendering. Clicca il pulsante: registra di nuovo solo PlainReader, perché il suo context ha ricevuto un nuovo oggetto. useMemo consegna a MemoContext lo stesso oggetto finché name non cambia, quindi MemoReader resta fermo. Questo conta solo quando il rendering diventa lento; per un'app piccola, un nuovo oggetto a ogni rendering va bene.
Dividere i context
Un valore che cambia spesso e un valore che non cambia mai non dovrebbero condividere un context. Se il setter vive insieme allo stato, un componente che deve solo chiamare setUser esegue comunque un nuovo render ogni volta che user cambia. Mettili in due context e ogni consumer si iscrive solo a ciò che legge:
const UserContext = createContext(null);
const SetUserContext = createContext(() => {});
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<SetUserContext value={setUser}>
<UserContext value={user}>{children}</UserContext>
</SetUserContext>
);
}
Il setUser di useState mantiene la stessa identità per sempre, quindi i componenti che leggono solo SetUserContext non eseguono mai un nuovo render a causa di un login. La stessa idea vale per dati non correlati: un tema e un carrello della spesa vanno in context separati, non in un unico grande AppContext.
Un tocco finale comune è un hook personalizzato come useUser() che chiama useContext(UserContext) e genera un errore chiaro quando manca il provider, così chi lo usa non importa mai direttamente l'oggetto context.
Context, props o una libreria di stato
- Le props sono la scelta predefinita. Passare un valore due o tre livelli più in basso è chiaro e facile da seguire.
- Il context va bene per valori di cui hanno bisogno molti componenti a molte profondità e che cambiano di rado: tema, lingua, l'utente che ha effettuato l'accesso, un oggetto di feature flag.
- Una libreria di stato (Redux Toolkit, Zustand, Jotai e altre) va bene per uno stato grande che si aggiorna spesso, dove vuoi che un componente si iscriva a una sola porzione e salti i rendering causati dal resto. Il context non ha un selettore di questo tipo: un consumer esegue un nuovo render a qualsiasi cambiamento del valore.
Prima di ricorrere al context o a una libreria, verifica se passare componenti come children elimina il drilling. Spesso lo fa, senza alcuna nuova API.
Domande frequenti
Cosa fa useContext in React?
Restituisce il valore attuale di un context, preso dal provider più vicino sopra il componente nell'albero. Quando il valore di quel provider cambia, ogni componente che lo legge esegue un nuovo render.
A cosa serve il valore predefinito in createContext?
È ciò che useContext restituisce quando non c'è alcun provider sopra il componente. È utile per i test e per i componenti renderizzati da soli, e non cambia mai.
In React 19 mi serve ancora Context.Provider?
No. In React 19 puoi renderizzare il context stesso come provider: <ThemeContext value="dark">. <ThemeContext.Provider value="dark"> funziona ancora, quindi il codice più vecchio continua a girare.
Come aggiorno il valore di un context da un componente figlio?
Tieni il valore nello stato del componente che renderizza il provider, e metti il setter nel context accanto al valore: value={{ theme, setTheme }}. Il figlio legge setTheme con useContext e lo chiama.
Il context di React sostituisce Redux?
Il context passa un valore lungo l'albero; da solo non gestisce lo stato. Combinato con useState o useReducer basta a molte app. Una libreria di stato aggiunge cose che il context non ha, come iscriversi a una sola porzione così che aggiornamenti non correlati non facciano renderizzare un componente, più devtools e middleware.