Sollevare lo stato significa spostarlo dai componenti che devono condividerlo al loro genitore comune più vicino. Il genitore tiene il valore, lo passa verso il basso come prop e passa una funzione che i figli chiamano per cambiarlo, così ogni figlio si renderizza a partire da un'unica copia dello stato.
Clicca "Show" su un pannello qualsiasi: si apre e quello che era aperto si chiude. Può essere aperto un solo pannello alla volta perché non sono i pannelli a deciderlo da soli. App contiene openIndex, e ogni Panel riceve solo isOpen e una funzione onOpen.
Il problema: uno stato che dovrebbe concordare
Parti dalla versione più ovvia, in cui ogni pannello possiede il proprio stato isOpen. Ogni pannello funziona, ma i pannelli non sanno nulla l'uno dell'altro, quindi niente impedisce che due di loro siano aperti insieme.
Apri entrambi i pannelli: restano aperti insieme. Lo stato in React è privato del componente che lo dichiara, quindi un fratello non può leggerlo né azzerarlo. Quando due componenti devono concordare, lo stato deve vivere sopra entrambi.
Sollevare lo stato in tre passaggi
Trasformare il secondo esempio nel primo richiede tre modifiche.
- Togli lo stato dal figlio. Elimina
useStateinPanele leggi inveceisOpendalle props. Il figlio non decide più se è aperto. - Passa dal genitore il valore e un modo per cambiarlo.
PanelriceveisOpene una callbackonOpen. Il figlio chiamaonOpen()quando il suo pulsante viene cliccato; non sa cosa ne faccia il genitore. - Aggiungi lo stato al genitore comune.
AppdichiaraopenIndexe lo trasforma in props per ogni pannello:isOpen={openIndex === 1}eonOpen={() => setOpenIndex(1)}.
Il genitore comune più vicino è il componente più in basso che renderizza tutti i componenti che hanno bisogno dello stato. Qui è App. Se i pannelli stessero dentro un componente Faq, lo stato andrebbe in Faq, non più in alto.
Dopo averlo sollevato, Panel è controllato dal suo genitore nello stesso senso di un input controllato: mostra ciò che dicono le props e segnala le modifiche tramite una callback. La pagina su componenti controllati e non controllati tratta la stessa idea per gli elementi dei form.
Un'unica fonte di verità
Quando due parti dello schermo mostrano lo stesso fatto, memorizza quel fatto una sola volta e calcola tutto il resto a partire da esso. Un convertitore di temperatura è il caso classico: gli input in Celsius e Fahrenheit devono sempre concordare, quindi non possono tenere ciascuno il proprio numero.
Scrivi in uno dei due campi e l'altro lo segue. Lo stato è un unico fatto: il numero che l'utente ha digitato per ultimo e in quale scala. L'altro campo viene calcolato da esso durante il rendering, quindi il campo in cui stai scrivendo mantiene sempre esattamente ciò che hai digitato. Cambia lo stato iniziale in { value: '212', scale: 'f' } e il messaggio sotto gli input diventa "Water boils."
Tenere un numero in Celsius e uno in Fahrenheit in due pezzi di stato significherebbe che ogni gestore deve aggiornarli entrambi, e la prima volta che un gestore se ne dimentica, i due input non concordano più. Un unico valore memorizzato non può essere in disaccordo con se stesso.
Passare il setter verso il basso
Un figlio può cambiare lo stato del genitore solo tramite una funzione che il genitore gli dà. Puoi passare il setter stesso (onSelect={setColor}) o una funzione che fa di più (onOpen={() => setOpenIndex(1)}). Dare alla prop un nome in stile evento come onSelect o onChange, invece di setSelected, lascia il figlio all'oscuro di come il genitore memorizza il valore, così il genitore può cambiarlo in seguito senza toccare il figlio.
ColorPicker e Preview non si parlano mai. Il selettore segnala una scelta verso l'alto, App la memorizza e il nuovo valore scorre verso il basso fino a entrambi.
Quando non sollevare lo stato
Sollevare ha un costo. Quando lo stato vive in un genitore, ogni modifica renderizza il genitore e, per default, tutti i suoi figli, compresi quelli che non usano lo stato. Sollevalo solo fino al genitore comune più vicino, e lascia lo stato usato da un solo componente dentro quel componente.
Scrivi nella casella e guarda la console: a ogni tasto si renderizza solo SearchBox. Ora sposta query in App e passalo a SearchBox come props. A ogni tasto viene quindi registrato anche ProductList, anche se la lista non usa la query. Se la lista filtrasse per query, sollevare sarebbe la scelta giusta, perché entrambi i componenti dipenderebbero dallo stesso valore.
La domanda da porsi è "chi ha bisogno di leggere questo valore?". Se la risposta è un solo componente, lo stato resta lì. Se sono diversi, va al loro genitore comune più vicino.
Quando sollevare va troppo oltre
A volte il genitore comune più vicino è molto in alto nell'albero, e il valore deve passare attraverso diversi componenti che si limitano a inoltrarlo. Questo è il prop drilling. Qualche livello di props va bene ed è facile da seguire. Quando lo stesso valore attraversa molti livelli, o quasi ogni componente ne ha bisogno (l'utente che ha effettuato l'accesso, il tema, la lingua), leggilo con useContext invece di passarlo a mano. Il context cambia il modo in cui il valore raggiunge i figli; lo stato vive comunque in un unico genitore, quindi è comunque sollevato.
Domande frequenti
Cosa significa sollevare lo stato in React?
Spostare un pezzo di stato dai componenti che lo usano al loro genitore comune più vicino. Il genitore possiede lo stato e passa ai figli, come props, il valore più una funzione per cambiarlo.
Come condividono lo stato due componenti fratelli in React?
I fratelli non possono leggere lo stato l'uno dell'altro. Metti lo stato nel loro genitore comune, passa il valore a entrambi e passa un setter (o un gestore come onChange) a quello che lo cambia. Entrambi i fratelli si renderizzano quindi a partire dallo stesso valore.
Come fa un componente figlio ad aggiornare lo stato del genitore?
Il genitore passa una funzione come prop, per esempio onSelect={setSelected} oppure onSelect={(id) => setSelected(id)}, e il figlio la chiama. Lo stato resta nel genitore; il figlio chiede solo la modifica.
Quando non dovrei sollevare lo stato?
Quando lo stato è usato da un solo componente. Sollevarlo più in alto del necessario fa renderizzare il genitore a ogni modifica e sparge props attraverso componenti a cui non interessano. Tieni lo stato il più vicino possibile a dove viene usato.
Qual è l'alternativa al sollevare lo stato troppo in alto?
Se ti ritrovi a passare le stesse props attraverso molti livelli, leggi invece il valore condiviso con il context (useContext), oppure ristruttura in modo che i componenti che ne hanno bisogno stiano più vicini.