Elevar o estado significa tirar o estado dos componentes que precisam compartilhá-lo e levá-lo para o pai comum mais próximo deles. O pai guarda o valor, passa-o para baixo como prop e passa uma função que os filhos chamam para mudá-lo, de modo que todos os filhos renderizam a partir de uma única cópia do estado.
Clique em "Show" em qualquer painel: ele abre e o que estava aberto fecha. Só um painel pode ficar aberto por vez porque os painéis não decidem isso sozinhos. App guarda openIndex, e cada Panel só recebe isOpen e uma função onOpen.
O problema: estado que precisa concordar
Comece pela versão óbvia, em que cada painel é dono do próprio estado isOpen. Cada painel funciona, mas os painéis não sabem nada uns dos outros, então nada impede que dois deles fiquem abertos ao mesmo tempo.
Abra os dois painéis: eles ficam abertos juntos. O estado no React é privado do componente que o declara, então um irmão não consegue lê-lo nem reiniciá-lo. Quando dois componentes precisam concordar, o estado tem que morar acima dos dois.
Elevando o estado em três passos
Transformar o segundo exemplo no primeiro leva três edições.
- Remova o estado do filho. Apague o
useStateemPanele leiaisOpendas props. O filho não decide mais se está aberto. - Passe do pai o valor e uma forma de mudá-lo.
PanelrecebeisOpene um callbackonOpen. O filho chamaonOpen()quando o botão dele é clicado; ele não sabe o que o pai faz com isso. - Adicione o estado ao pai em comum.
AppdeclaraopenIndexe o transforma em props para cada painel:isOpen={openIndex === 1}eonOpen={() => setOpenIndex(1)}.
O pai comum mais próximo é o componente mais baixo que renderiza todos os componentes que precisam do estado. Aqui é App. Se os painéis estivessem dentro de um componente Faq, o estado iria para Faq, não mais para cima.
Depois da elevação, Panel é controlado pelo pai no mesmo sentido de um input controlado: ele mostra o que as props dizem e informa mudanças por um callback. A página sobre componentes controlados vs não controlados trata da mesma ideia para elementos de formulário.
Uma fonte única da verdade
Quando duas partes da tela mostram o mesmo fato, guarde esse fato uma vez e calcule todo o resto a partir dele. Um conversor de temperatura é o caso clássico: os inputs de Celsius e Fahrenheit precisam sempre concordar, então não podem cada um guardar o próprio número.
Digite em qualquer um dos campos e o outro acompanha. O estado é um fato só: o número que o usuário digitou por último e em qual escala estava. O outro campo é calculado a partir dele durante a renderização, então o campo em que você está digitando sempre mantém exatamente o que você digitou. Troque o estado inicial por { value: '212', scale: 'f' } e a mensagem abaixo dos inputs muda para "Water boils."
Guardar um número em Celsius e outro em Fahrenheit em duas partes de estado significaria que todo handler precisa atualizar os dois, e na primeira vez que um handler esquecer, os dois inputs discordam. Um único valor guardado não consegue discordar de si mesmo.
Passando o setter para baixo
Um filho só consegue mudar o estado do pai por meio de uma função que o pai lhe dá. Você pode passar o próprio setter (onSelect={setColor}) ou uma função que faz mais coisas (onOpen={() => setOpenIndex(1)}). Dar à prop um nome no estilo de evento como onSelect ou onChange, em vez de setSelected, mantém o filho sem saber como o pai guarda o valor, e assim o pai pode mudar isso depois sem mexer no filho.
ColorPicker e Preview nunca conversam entre si. O seletor informa uma escolha para cima, App a guarda, e o novo valor desce para os dois.
Quando não elevar o estado
Elevar tem um custo. Quando o estado mora em um pai, toda mudança renderiza o pai e, por padrão, todos os filhos dele, inclusive os que não usam o estado. Eleve só até o pai comum mais próximo e deixe o estado que só um componente usa dentro desse componente.
Digite na caixa e observe o console: só SearchBox renderiza a cada tecla. Agora mova query para cima, para App, e passe-o para SearchBox como props. Cada tecla passa então a registrar também ProductList, mesmo que a lista não use a busca. Se a lista filtrasse pela busca, elevar seria a decisão certa, já que os dois componentes dependeriam do mesmo valor.
A pergunta a fazer é "quem precisa ler este valor?" Se a resposta é um componente, o estado fica nele. Se são vários, ele vai para o pai comum mais próximo deles.
Quando a elevação vai longe demais
Às vezes o pai comum mais próximo fica bem alto na árvore, e o valor precisa passar por vários componentes que só o repassam. Isso é prop drilling. Algumas camadas de props não são problema e são fáceis de acompanhar. Quando o mesmo valor atravessa muitas camadas, ou quase todo componente precisa dele (o usuário logado, o tema, o idioma), leia-o com useContext em vez de passá-lo à mão. O context muda como o valor chega aos filhos; o estado em si continua morando em um pai, então continua elevado.
Perguntas frequentes
O que significa elevar o estado no React?
Mover uma parte do estado dos componentes que a usam para o pai comum mais próximo deles. O pai é dono do estado e passa o valor, junto com uma função para mudá-lo, para os filhos como props.
Como dois componentes irmãos compartilham estado no React?
Irmãos não conseguem ler o estado um do outro. Coloque o estado no pai em comum, passe o valor para os dois e passe um setter (ou um handler como onChange) para aquele que o altera. Os dois irmãos então renderizam a partir do mesmo valor.
Como um componente filho atualiza o estado do pai?
O pai passa uma função como prop, por exemplo onSelect={setSelected} ou onSelect={(id) => setSelected(id)}, e o filho a chama. O estado continua no pai; o filho só pede a mudança.
Quando não devo elevar o estado?
Quando só um componente usa o estado. Elevá-lo mais alto que o necessário faz o pai renderizar a cada mudança e espalha props por componentes que não se importam com elas. Mantenha o estado o mais perto possível de onde ele é usado.
Qual é a alternativa a elevar demais o estado?
Se você se pega passando as mesmas props por muitas camadas, leia o valor compartilhado com context (useContext) ou reorganize os componentes para que os que precisam dele fiquem mais próximos.