Compartilhando State Entre Componentes
Às vezes, você deseja que o state de dois componentes seja sempre alterado em conjunto. Para fazer isso, remova o state de ambos, mova-os para o pai comum mais próximo e, em seguida, passe-os aos componentes por meio de props. Isto é conhecido como elevação de state e é uma das coisas mais comuns que você fará ao escrever código React.
Você aprenderá
- Como compartilhar state entre componentes por sua elevação
- O que são componentes controlados e não controlados
Exemplificando a elevação de state
Neste exemplo, um componente Accordion
pai renderiza dois componentes Panel
separados:
Accordion
Panel
Panel
Cada componente Panel
tem um state booleano isActive
que determina se o seu conteúdo está visível.
Pressione o botão “Show” de ambos os painéis:
import { useState } from 'react'; function Panel({ title, children }) { const [isActive, setIsActive] = useState(false); return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); } export default function Accordion() { return ( <> <h2>Almaty, Cazaquistão</h2> <Panel title="Sobre"> Com uma população de cerca de 2 milhões de habitantes, Almaty é a maior cidade do Cazaquistão. De 1929 a 1997, foi sua capital. </Panel> <Panel title="Etimologia"> O nome vem de <span lang="kk-KZ">алма</span>, a palavra cazaque para "maçã", e é frequentemente traduzido como "cheio de maçãs". De fato, acredita-se que a região em torno de Almaty seja o lar ancestral da maçã, e o <i lang="la">Malus sieversii</i> selvagem é considerado um provável candidato a ancestral da maçã doméstica moderna. </Panel> </> ); }
Observe como o fato de pressionar o botão de um painel não afeta o outro painel—eles são independentes.
Mas agora digamos que você queira alterá-lo para que apenas um painel seja expandido por vez. Com esse design, expandir o segundo painel deve recolher o primeiro. Como você faria isso?
Para coordenar esses dois painéis, você precisa “elevar o state deles” para um componente pai em três etapas:
- Remover o state dos componentes filhos.
- Passar dados estáticos do pai comum.
- Adicionar o state ao pai comum e passá-lo para baixo junto com os manipuladores de eventos.
Isso permitirá que o componente Accordion
coordene ambos os Panel
s e expanda apenas um de cada vez.
Etapa 1: Remover o state dos componentes filhos
Você dará o controle do isActive
do Panel
ao seu componente pai. Isso significa que o componente pai passará o isActive
para o Panel
como uma propriedade. Comece removendo essa linha do componente Panel
:
const [isActive, setIsActive] = useState(false);
Em vez disso, adicione isActive
à lista de props do Panel
:
function Panel({ title, children, isActive }) {
Agora, o componente pai do Panel
pode controlar o isActive
transmitindo-o como uma prop. Por outro lado, o componente Panel
agora não tem nenhum controle sobre o valor de isActive
; isso agora depende do componente pai!
Etapa 2: passar dados estáticos do pai comum
Para elevar o state, você deve localizar o componente pai comum mais próximo de ambos os componentes filhos que deseja coordenar:
Accordion
(pai comum mais próximo)Panel
Panel
Neste exemplo, é o componente Accordion
. Como ele está acima de ambos os painéis e pode controlar suas props, ele se tornará a “fonte da verdade” para saber qual painel está ativo no momento. Faça com que o componente Accordion
passe um valor estático de isActive
(por exemplo, true
) para ambos os painéis:
import { useState } from 'react'; export default function Accordion() { return ( <> <h2>Almaty, Cazaquistão</h2> <Panel title="Sobre" isActive={true}> Com uma população de cerca de 2 milhões de habitantes, Almaty é a maior cidade do Cazaquistão. De 1929 a 1997, foi sua capital. </Panel> <Panel title="Etmologia" isActive={true}> O nome vem de <span lang="kk-KZ">алма</span>, a palavra cazaque para "maçã", e é frequentemente traduzido como "cheio de maçãs". De fato, acredita-se que a região em torno de Almaty seja o lar ancestral da maçã, e o<i lang="la"> Malus sieversii</i> selvagem é considerado um provável candidato a ancestral da maçã doméstica moderna. </Panel> </> ); } function Panel({ title, children, isActive }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Exibir </button> )} </section> ); }
Tente editar os valores estáticos isActive
no componente Accordion
e veja o resultado na tela.
Etapa 3: Adicionar state ao pai comum
Elevar o state muitas vezes muda a natureza do que você está armazenando como state.
Nesse caso, apenas um painel deve estar ativo por vez. Isso significa que o componente pai comum Accordion
precisa manter o controle de qual painel está ativo. Em vez de um valor booleano
, ele poderia usar um número como índice do Panel
ativo para a variável do state:
const [activeIndex, setActiveIndex] = useState(0);
Quando o activeIndex
é 0
, o primeiro painel está ativo, e quando é 1
, é o segundo.
Clicar no botão “Exibir” em um dos “Painéis” precisa alterar o índice ativo no “Accordion”. Um Panel
não pode definir o state activeIndex
diretamente porque ele é definido dentro do Accordion
. O componente Accordion
precisa permitir explicitamente que o componente Panel
altere seu state, passando um manipulador de eventos como uma prop:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
O <button>
dentro do Panel
agora usará a propriedade onShow
como seu manipulador de eventos de clique:
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almaty, Cazaquistão</h2> <Panel title="Sobre" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > Com uma população de cerca de 2 milhões de habitantes, Almaty é a maior cidade do Cazaquistão. De 1929 a 1997, foi sua capital. </Panel> <Panel title="Etmologia" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > O nome vem de <span lang="kk-KZ">алма</span>, a palavra cazaque para "maçã", e é frequentemente traduzido como "cheio de maçãs". De fato, acredita-se que a região em torno de Almaty seja o lar ancestral da maçã, e o<i lang="la"> Malus sieversii</i> selvagem é considerado um provável candidato a ancestral da maçã doméstica moderna. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Exibir </button> )} </section> ); }
Isso completa a elevação do state! Mover o state para o componente pai comum permitiu que você coordenasse os dois painéis. Usar o índice ativo em vez de dois sinalizadores “é exibido” garantiu que apenas um painel estivesse ativo em um determinado momento. E passar o manipulador de eventos para o filho permitiu que ele alterasse o state do pai.
Deep Dive
É comum chamar um componente com algum state local de “não controlado”. Por exemplo, o componente Panel
original com uma variável de state isActive
não é controlado porque seu pai não pode influenciar se o painel está ativo ou não.
Por outro lado, pode-se dizer que um componente é “controlado” quando as informações importantes nele contidas são orientadas por props em vez de seu próprio state local. Isso permite que o componente pai especifique totalmente seu comportamento. O componente final Panel
com a prop isActive
é controlado pelo componente Accordion
.
Os componentes não controlados são mais fáceis de usar dentro de seus pais porque exigem menos configuração. Mas são menos flexíveis quando você deseja coordená-los entre si. Os componentes controlados são extremamente flexíveis, mas exigem que os componentes principais os configurem totalmente com props.
Na prática, “controlado” e “não controlado” não são termos técnicos rigorosos - cada componente geralmente tem uma mistura de state local e props. No entanto, essa é uma maneira útil de falar sobre como os componentes são projetados e quais recursos eles oferecem.
Ao escrever um componente, considere quais informações nele devem ser controladas (por meio de props) e quais informações não devem ser controladas (por meio de state). Mas você sempre pode mudar de ideia e refatorar mais tarde.
Uma única fonte de verdade para cada state
Em um aplicativo React, muitos componentes terão seu próprio state. Alguns states podem “residir” perto dos componentes de folha (componentes na parte inferior da árvore), como entradas. Outro state pode “residir” mais perto do topo do aplicativo. Por exemplo, até mesmo as bibliotecas de roteamento do lado do cliente geralmente são implementadas armazenando a rota atual no state do React e passando-a para baixo por meio de props!
Para cada parte exclusiva do state, você escolherá o componente que o “possui”. Esse princípio também é conhecido como ter uma “única fonte de verdade”. Isso não significa que todo o state esteja em um único lugar, mas que, para cada parte do state, há um componente específico que contém essa parte da informação. Em vez de duplicar o state compartilhado entre os componentes, levante-o para o pai compartilhado comum e passe-o para os filhos que precisam dele.
Seu aplicativo mudará à medida que você trabalhar nele. É comum que você mova o state para baixo ou para cima enquanto ainda está descobrindo onde cada parte do state “reside”. Tudo isso faz parte do processo!
Para ver como isso funciona na prática com mais alguns componentes, leia Pensando em React.
Recap
- Quando você quiser coordenar dois componentes, mova o state deles para o pai comum.
- Em seguida, passe as informações por meio de props vindas de seu pai comum.
- Por fim, passe os manipuladores de eventos para que os filhos possam alterar o state do pai.
- É útil considerar os componentes como “controlados” (acionados por props) ou “não controlados” (acionados pelo state).
Challenge 1 of 2: Entradas sincronizadas
Essas duas entradas são independentes. Faça com que elas fiquem sincronizadas: a edição de uma entrada deve atualizar a outra entrada com o mesmo texto e vice-versa.
import { useState } from 'react'; export default function SyncedInputs() { return ( <> <Input label="First input" /> <Input label="Second input" /> </> ); } function Input({ label }) { const [text, setText] = useState(''); function handleChange(e) { setText(e.target.value); } return ( <label> {label} {' '} <input value={text} onChange={handleChange} /> </label> ); }