useState è l'hook che permette a un componente di ricordare qualcosa tra un rendering e l'altro: un conteggio, il testo in un input, se un menu è aperto. Ogni volta che chiami il suo setter, React renderizza di nuovo il componente con il nuovo valore.
Clicca il pulsante, poi cambia useState(0) in useState(10) nell'editor. L'anteprima si ricostruisce e il conteggio parte da 10.
La sintassi
const [value, setValue] = useState(initialValue);
initialValueviene usato solo al primo rendering. A ogni rendering successivo React lo ignora e restituisce il valore che ha memorizzato.valueè il valore attuale per questo rendering.setValuesostituisce il valore e programma un nuovo render.
I nomi li scegli tu. useState restituisce un array di due elementi e le parentesi quadre lo spacchettano, quindi [isOpen, setIsOpen] e [email, setEmail] sono lo stesso schema.
Chiama useState al livello più alto del tuo componente, mai dentro un ciclo, una condizione o una funzione annidata. React abbina ogni chiamata al suo valore memorizzato in base all'ordine delle chiamate, quindi quell'ordine deve essere lo stesso a ogni rendering (vedi le regole degli hook).
Più pezzi di stato
Un componente può chiamare useState tutte le volte che gli serve. Ogni chiamata è un valore indipendente.
Lo stato è un'istantanea
Il setter non cambia la variabile che hai in mano. Dice a React quale deve essere il valore nel rendering successivo. Finché quel rendering non avviene, quello attuale continua a vedere il vecchio valore.
Apri la console sotto l'anteprima e clicca una volta. Il gestore registra 0, il vecchio valore, e poi il componente registra un nuovo rendering con 1. Non c'è nulla di sbagliato: count è una costante per tutto il rendering, e il rendering successivo ne riceve una nuova.
Funzioni di aggiornamento
A causa dell'istantanea, chiamare il setter tre volte con count + 1 aggiunge uno, non tre: ogni chiamata calcola 0 + 1. Passa invece una funzione e React fornisce a ogni chiamata il risultato della precedente.
Usa la forma con funzione di aggiornamento ogni volta che il valore successivo dipende da quello precedente e l'aggiornamento potrebbe non partire direttamente dal rendering più recente: più set nello stesso gestore, una callback di setInterval, o un effetto.
Oggetti e array nello stato
React decide se renderizzare di nuovo confrontando il vecchio e il nuovo valore con Object.is. Se modifichi un oggetto sul posto e ripassi lo stesso oggetto, il confronto dice che non è cambiato nulla e lo schermo resta com'era. Passa sempre un nuovo oggetto o array.
{ ...user, age: user.age + 1 } copia ogni campo e ne sovrascrive uno. [...tags, 'code'] aggiunge un elemento, e filter ne rimuove uno, restituendo entrambi un nuovo array. La pagina su aggiornare array e oggetti tratta gli oggetti annidati, la modifica di una voce in una lista e l'inserimento nel mezzo.
Stato iniziale lazy
L'argomento di useState viene valutato a ogni rendering, anche se React lo usa solo la prima volta. Quando calcolarlo è costoso (leggere localStorage, analizzare una stringa grande), passa una funzione. React la chiama una sola volta, al primo rendering.
// Runs readSavedNotes() on every render, then throws the result away
const [notes, setNotes] = useState(readSavedNotes());
// Runs it once
const [notes, setNotes] = useState(readSavedNotes);
const [notes, setNotes] = useState(() => readSavedNotes());
Azzerare lo stato con una key
Lo stato appartiene alla posizione di un componente nell'albero. Se nello stesso punto renderizzi una key diversa, React lo tratta come un nuovo componente e il suo stato riparte da capo. È il modo più pulito per azzerare un form quando cambia la cosa che modifica.
Scrivi un messaggio, poi cambia destinatario: la bozza si svuota. Elimina key={to} e riprova, e il testo resta, perché ora React vede lo stesso Draft nello stesso punto.
Errori comuni
Registrare lo stato subito dopo averlo impostato. Vedi il vecchio valore, come mostrato sopra. Registra nel corpo del componente, oppure calcola prima il valore successivo in una variabile e usa quella.
Modificare lo stato direttamente. user.age++; setUser(user) passa lo stesso oggetto, quindi non si renderizza nulla. Prima fai una copia.
Chiamare il setter durante il rendering. <button onClick={setCount(count + 1)}> chiama setCount durante il rendering, che provoca un nuovo render, che lo chiama di nuovo. React ferma questo ciclo con "Too many re-renders". Passa una funzione: onClick={() => setCount(count + 1)}.
Memorizzare valori che puoi calcolare. Se fullName è sempre first + ' ' + last, calcolalo durante il rendering invece di tenere un terzo pezzo di stato che può perdere la sincronia.
Domande frequenti
Cosa restituisce useState?
Un array con esattamente due elementi: il valore attuale e una funzione che lo sostituisce. Li chiami tu con la destrutturazione degli array, per convenzione [thing, setThing].
Perché il mio stato non si aggiorna subito?
Chiamare il setter non cambia la variabile che hai già. Chiede a React di renderizzare di nuovo il componente, e il nuovo valore compare in quel rendering successivo. Dentro il gestore di eventi attuale c'è ancora il vecchio valore, quindi console.log(count) subito dopo setCount(count + 1) stampa il vecchio numero.
Quando dovrei passare una funzione al setter?
Quando il nuovo valore dipende da quello vecchio e lo imposti più di una volta, oppure da dentro un timer o un effetto. setCount(c => c + 1) parte sempre dal valore più recente, mentre setCount(count + 1) parte dal valore visto da questo rendering.
useState può contenere un oggetto o un array?
Sì, ma trattalo come di sola lettura. Costruisci un nuovo oggetto o array e passalo al setter (setUser({ ...user, name }), setItems([...items, item])). Modificare quello esistente sul posto non provoca un rendering.
Qual è la differenza tra useState e useReducer?
Entrambi memorizzano stato. useState va bene per pochi valori indipendenti. useReducer sposta la logica di aggiornamento in un'unica funzione che riceve un'azione, il che si legge meglio quando molti eventi cambiano lo stesso stato in modi correlati.