Escolhendo a Estrutura do State
Estruturar bem o state pode fazer a diferença entre um componente que é agradável de modificar e depurar, e um que é uma fonte constante de erros. Aqui estão algumas dicas que você deve considerar ao estruturar estados.
Você aprenderá
- Quando usar uma única variável de state versus várias
- O que evitar ao organizar estados
- Como corrigir problemas comuns na estrutura do state
Princípios para estruturar estados
Quando você escreve um componente que mantém algum state, você terá que fazer escolhas sobre quantas variáveis de state usar e qual deve ser a forma dos dados. Embora seja possível escrever programas corretos mesmo com uma estrutura de state subótima, existem alguns princípios que podem orientá-lo a fazer escolhas melhores:
- Agrupe estados relacionados. Se você sempre atualiza duas ou mais variáveis de state ao mesmo tempo, considere uni-las em uma única variável de state.
- Evite contradições no state. Quando o state é estruturado de forma que várias partes do state possam se contradizer e “discordar” umas das outras, você deixa espaço para erros. Tente evitar isso.
- Evite estados redundantes. Se você puder calcular algumas informações das props do componente ou de suas variáveis de state existentes durante a renderização, não coloque essas informações no state desse componente.
- Evite duplicação no state. Quando os mesmos dados são duplicados entre várias variáveis de state, ou dentro de objetos aninhados, é difícil mantê-los sincronizados. Reduza a duplicação quando puder.
- Evite estados muito aninhados. Um state muito hierárquico não é muito conveniente para atualizar. Quando possível, prefira estruturar o state de forma plana.
O objetivo por trás destes princípios é tornar o state fácil de atualizar sem introduzir erros. Remover dados redundantes e duplicados do state ajuda a garantir que todas as suas partes permaneçam sincronizadas. Isso é semelhante a como um engenheiro de banco de dados pode querer “normalizar” a estrutura do banco de dados para reduzir a chance de erros. Parafraseando Albert Einstein, “Faça seu state o mais simples possível - mas não simples demais.”
Agora vamos ver como estes princípios se aplicam na prática.
Agrupe estados relacionados
As vezes você pode ficar em dúvida entre usar uma única variável de state, ou várias.
Você deveria fazer isto?
const [x, setX] = useState(0);
const [y, setY] = useState(0);
Ou isto?
const [position, setPosition] = useState({ x: 0, y: 0 });
Tecnicamente, você pode usar qualquer uma dessas abordagens. Mas se duas variáveis de state sempre mudam juntas, pode ser uma boa ideia uní-las em uma única variável de state. Assim você não esquecerá de sempre mantê-las sincronizadas, como neste exemplo onde mover o cursor atualiza ambas as coordenadas do ponto vermelho:
import { useState } from 'react'; export default function MovingDot() { const [position, setPosition] = useState({ x: 0, y: 0 }); return ( <div onPointerMove={e => { setPosition({ x: e.clientX, y: e.clientY }); }} style={{ position: 'relative', width: '100vw', height: '100vh', }}> <div style={{ position: 'absolute', backgroundColor: 'red', borderRadius: '50%', transform: `translate(${position.x}px, ${position.y}px)`, left: -10, top: -10, width: 20, height: 20, }} /> </div> ) }
Outro caso em que você agrupará dados em um objeto ou em um array é quando você não sabe quantas variáveis de state vai precisar. Por exemplo, é útil quando você tem um formulário onde o usuário pode adicionar campos personalizados.
Evite contradições no state
Aqui está um formulário de feedback do hotel com as variáveis de state isSending
e isSent
:
import { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); const [isSent, setIsSent] = useState(false); async function handleSubmit(e) { e.preventDefault(); setIsSending(true); await sendMessage(text); setIsSending(false); setIsSent(true); } if (isSent) { return <h1>Obrigado pelo feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>Como foi sua estadia no Pônei Saltitante?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Enviar </button> {isSending && <p>Enviando...</p>} </form> ) } // Simula o envio de uma mensagem. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Embora este código funcione, ele deixa a porta aberta para estados “impossíveis”. Por exemplo, se você esquecer de chamar setIsSent
e setIsSending
juntos, você pode acabar em uma situação onde tanto isSending
quanto isSent
são true
ao mesmo tempo. Quão mais complexo for o seu componente, mais difícil será entender o que aconteceu.
Como isSending
e isSent
nunca devem ser true
ao mesmo tempo, é melhor substituí-los por uma variável de state status
que pode assumir um de três estados válidos: 'typing'
(inicial), 'sending'
e 'sent'
:
import { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [status, setStatus] = useState('typing'); async function handleSubmit(e) { e.preventDefault(); setStatus('sending'); await sendMessage(text); setStatus('sent'); } const isSending = status === 'sending'; const isSent = status === 'sent'; if (isSent) { return <h1>Obrigado pelo feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>Como foi sua estadia no Pônei Saltitante?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Enviar </button> {isSending && <p>Enviando...</p>} </form> ); } // Simula o envio de uma mensagem. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Voce ainda pode declarar algumas constantes para legibilidade:
const isSending = status === 'sending';
const isSent = status === 'sent';
Mas elas não são variáveis de state, então você não precisa se preocupar com elas ficando fora de sincronia uma com a outra.
Evite estados redundantes
Se você pode calcular algumas informações das props do componente ou de suas variáveis de state existentes durante a renderização, você não deveria colocar essas informações no state desse componente.
Por exemplo, neste formulário. Ele funciona, mas você consegue encontrar algum state redundante nele?
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> <h2>Vamos fazer seu check-in</h2> <label> Primeiro nome:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Sobrenome:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Seu ticket será emitido para: <b>{fullName}</b> </p> </> ) }
Este formulário tem três variáveis de state: firstName
, lastName
e fullName
. No entanto, fullName
é redundante. Você sempre pode calcular fullName
a partir de firstName
e lastName
durante a renderização, então remova-o do state.
Você pode fazer desta forma:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> <h2>Vamos fazer seu check-in</h2> <label> Primeiro nome:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Sobrenome:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Seu ticket será emitido para: <b>{fullName}</b> </p> </> ); }
Aqui, fullName
não é uma variável de state. Em vez disso, ela é calculada durante a renderização:
const fullName = firstName + ' ' + lastName;
Como resultado, os manipuladores de mudança não precisam fazer nada de especial para atualizá-lo. Quando você chama setFirstName
ou setLastName
, você dispara uma nova renderização, e então o próximo fullName
será calculado a partir dos dados atualizados.
Deep Dive
Um exemplo comum de state redundante são códigos como este:
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor);
Aqui, uma variável de state color
é inicializada com a prop messageColor
. O problema é que se o componente pai passar um valor diferente para messageColor
depois (por exemplo, 'red'
ao invés de 'blue'
), a variável de state color
não seria atualizada! O state é inicializado apenas durante a primeira renderização.
É por isso que “espelhar” alguma prop em uma variável de state pode levar a confusão. Em vez disso, use a prop messageColor
diretamente no seu código. Se você quiser dar um nome mais curto para ela, use uma constante:
function Message({ messageColor }) {
const color = messageColor;
Desta forma, ela não ficará fora de sincronia com a prop passada pelo componente pai.
“Espelhar” props no state só faz sentido quando você quer ignorar todas as atualizações para uma prop específica. Por convenção, comece o nome da prop com initial
ou default
para deixar claro que seus novos valores são ignorados:
function Message({ initialColor }) {
// A variável de *state* `color` guarda o *primeiro* valor de `initialColor`.
// Mudanças posteriores na *prop* `initialColor` são ignoradas.
const [color, setColor] = useState(initialColor); */
Evite duplicação no state
Este componente de lista de menus permite que você escolha um único lanche de viagem dentre vários:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'alga crocante', id: 1 }, { title: 'barra de granola', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); return ( <> <h2>Qual o seu lanche de viagem?</h2> <ul> {items.map(item => ( <li key={item.id}> {item.title} {' '} <button onClick={() => { setSelectedItem(item); }}>Escolha</button> </li> ))} </ul> <p>Você selecionou {selectedItem.title}.</p> </> ); }
No momento, ele armazena o item selecionado como um objeto na variável de state selectedItem
. No entanto, isso não é bom: o conteúdo de selectedItem
é o mesmo objeto que um dos itens dentro da lista items
. Isso significa que as informações sobre o item em si estão duplicadas em dois lugares.
Por que isso é um problema? Vamos tornar cada item editável:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'alga crocante', id: 1 }, { title: 'barra de granola', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> <h2>Qual o seu lanche de viagem?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedItem(item); }}>Escolha</button> </li> ))} </ul> <p>Você selecionou {selectedItem.title}.</p> </> ); }
Observe como se você clicar primeiro em “Escolha” em um item e depois editá-lo, a entrada é atualizada, mas o rótulo na parte inferior não reflete as edições. Isso ocorre porque você duplicou o state e esqueceu de atualizar selectedItem
.
Embora você pudesse atualizar selectedItem
também, uma correção mais fácil é remover a duplicação. Neste exemplo, em vez de um objeto selectedItem
(que cria uma duplicação com objetos dentro de items
), você mantém o selectedId
no state e depois obtém o selectedItem
pesquisando o array items
por um item com esse ID:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'alga crocante', id: 1 }, { title: 'barra de granola', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedId, setSelectedId] = useState(0); const selectedItem = items.find(item => item.id === selectedId ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> <h2>Qual o seu lanche de viagem?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedId(item.id); }}>Escolha</button> </li> ))} </ul> <p>Você selecionou {selectedItem.title}.</p> </> ); }
(Alternativamente, você pode manter o índice selecionado no state.)
O state costumava ser duplicado assim:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedItem = {id: 0, title: 'pretzels'}
Mas depois da mudança, é assim:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedId = 0
A duplicação desapareceu, e você mantém apenas o state essencial!
Agora, se você editar o item selecionado, a mensagem abaixo será atualizada imediatamente. Isso ocorre porque setItems
dispara uma nova renderização, e items.find(...)
encontraria o item com o título atualizado. Você não precisava manter o item selecionado no state, porque apenas o ID selecionado é essencial. O resto poderia ser calculado durante a renderização.
Evite estados muito aninhados
Imagine um plano de viagem consistindo de planetas, continentes e países. Você pode ser tentado estruturar seu state usando objetos e arrays aninhados, como neste exemplo:
export const initialTravelPlan = { id: 0, title: '(Root)', childPlaces: [{ id: 1, title: 'Terra', childPlaces: [{ id: 2, title: 'África', childPlaces: [{ id: 3, title: 'Botsuana', childPlaces: [] }, { id: 4, title: 'Egito', childPlaces: [] }, { id: 5, title: 'Kênia', childPlaces: [] }, { id: 6, title: 'Madagascar', childPlaces: [] }, { id: 7, title: 'Marrocos', childPlaces: [] }, { id: 8, title: 'Nigéria', childPlaces: [] }, { id: 9, title: 'África do Sul', childPlaces: [] }] }, { id: 10, title: 'Ámericas', childPlaces: [{ id: 11, title: 'Argentina', childPlaces: [] }, { id: 12, title: 'Brasil', childPlaces: [] }, { id: 13, title: 'Barbados', childPlaces: [] }, { id: 14, title: 'Canadá', childPlaces: [] }, { id: 15, title: 'Jamaica', childPlaces: [] }, { id: 16, title: 'México', childPlaces: [] }, { id: 17, title: 'Trindade e Tobago', childPlaces: [] }, { id: 18, title: 'Venezuela', childPlaces: [] }] }, { id: 19, title: 'Ásia', childPlaces: [{ id: 20, title: 'China', childPlaces: [] }, { id: 21, title: 'Hong Kong', childPlaces: [] }, { id: 22, title: 'Índia', childPlaces: [] }, { id: 23, title: 'Singapura', childPlaces: [] }, { id: 24, title: 'Coreia do Sul', childPlaces: [] }, { id: 25, title: 'Tailândia', childPlaces: [] }, { id: 26, title: 'Vietnã', childPlaces: [] }] }, { id: 27, title: 'Europa', childPlaces: [{ id: 28, title: 'Croácia', childPlaces: [], }, { id: 29, title: 'França', childPlaces: [], }, { id: 30, title: 'Alemanha', childPlaces: [], }, { id: 31, title: 'Itália', childPlaces: [], }, { id: 32, title: 'Portugal', childPlaces: [], }, { id: 33, title: 'Espanha', childPlaces: [], }, { id: 34, title: 'Turquia', childPlaces: [], }] }, { id: 35, title: 'Oceania', childPlaces: [{ id: 36, title: 'Austrália', childPlaces: [], }, { id: 37, title: 'Bora Bora (Polinésia Francesa)', childPlaces: [], }, { id: 38, title: 'Ilha da Páscoa (Chile)', childPlaces: [], }, { id: 39, title: 'Fiji', childPlaces: [], }, { id: 40, title: 'Hawaii (EUA)', childPlaces: [], }, { id: 41, title: 'Nova Zelândia', childPlaces: [], }, { id: 42, title: 'Vanuatu', childPlaces: [], }] }] }, { id: 43, title: 'Lua', childPlaces: [{ id: 44, title: 'Rheita', childPlaces: [] }, { id: 45, title: 'Piccolomini', childPlaces: [] }, { id: 46, title: 'Tycho', childPlaces: [] }] }, { id: 47, title: 'Marte', childPlaces: [{ id: 48, title: 'Cidade do Milho', childPlaces: [] }, { id: 49, title: 'Monte Verde', childPlaces: [] }] }] };
Agora, digamos que você queira adicionar um botão para excluir um lugar que você já visitou. Como você faria isso? Atualizar estados aninhados envolve fazer cópias de objetos desde a parte que mudou. Excluir um lugar profundamente aninhado envolveria copiar toda a cadeia de lugares pai. Esse código pode ser muito verboso.
Se o state for muito aninhado para ser atualizado facilmente, considere torná-lo “plano”. Aqui está uma maneira de você reestruturar esses dados. Em vez de uma estrutura em forma de árvore em que cada place
tem um array de seus lugares filhos, você pode fazer com que cada lugar mantenha um array de IDs dos seus lugares filhos. Em seguida, armazene um mapeamento de cada ID de lugar para o lugar correspondente.
Essa reestruturação de dados pode lembrá-lo de ver uma tabela de banco de dados:
export const initialTravelPlan = { 0: { id: 0, title: '(Root)', childIds: [1, 43, 47], }, 1: { id: 1, title: 'Terra', childIds: [2, 10, 19, 27, 35] }, 2: { id: 2, title: 'África', childIds: [3, 4, 5, 6 , 7, 8, 9] }, 3: { id: 3, title: 'Botsuana', childIds: [] }, 4: { id: 4, title: 'Egito', childIds: [] }, 5: { id: 5, title: 'Kênia', childIds: [] }, 6: { id: 6, title: 'Madagascar', childIds: [] }, 7: { id: 7, title: 'Marrocos', childIds: [] }, 8: { id: 8, title: 'Nigéria', childIds: [] }, 9: { id: 9, title: 'África do Sul', childIds: [] }, 10: { id: 10, title: 'Américas', childIds: [11, 12, 13, 14, 15, 16, 17, 18], }, 11: { id: 11, title: 'Argentina', childIds: [] }, 12: { id: 12, title: 'Brasil', childIds: [] }, 13: { id: 13, title: 'Barbados', childIds: [] }, 14: { id: 14, title: 'Canadá', childIds: [] }, 15: { id: 15, title: 'Jamaica', childIds: [] }, 16: { id: 16, title: 'México', childIds: [] }, 17: { id: 17, title: 'Trindade e Tobago', childIds: [] }, 18: { id: 18, title: 'Venezuela', childIds: [] }, 19: { id: 19, title: 'Ásia', childIds: [20, 21, 22, 23, 24, 25, 26], }, 20: { id: 20, title: 'China', childIds: [] }, 21: { id: 21, title: 'Hong Kong', childIds: [] }, 22: { id: 22, title: 'Índia', childIds: [] }, 23: { id: 23, title: 'Singapura', childIds: [] }, 24: { id: 24, title: 'Coreia do Sul', childIds: [] }, 25: { id: 25, title: 'Tailândia', childIds: [] }, 26: { id: 26, title: 'Vietnã', childIds: [] }, 27: { id: 27, title: 'Europa', childIds: [28, 29, 30, 31, 32, 33, 34], }, 28: { id: 28, title: 'Croácia', childIds: [] }, 29: { id: 29, title: 'França', childIds: [] }, 30: { id: 30, title: 'Alemanha', childIds: [] }, 31: { id: 31, title: 'Itália', childIds: [] }, 32: { id: 32, title: 'Portugal', childIds: [] }, 33: { id: 33, title: 'Espanha', childIds: [] }, 34: { id: 34, title: 'Turquia', childIds: [] }, 35: { id: 35, title: 'Oceania', childIds: [36, 37, 38, 39, 40, 41, 42], }, 36: { id: 36, title: 'Austrália', childIds: [] }, 37: { id: 37, title: 'Bora Bora (Polinésia Francesa)', childIds: [] }, 38: { id: 38, title: 'Ilha de Páscoa (Chile)', childIds: [] }, 39: { id: 39, title: 'Fiji', childIds: [] }, 40: { id: 40, title: 'Hawaii (EUA)', childIds: [] }, 41: { id: 41, title: 'Nova Zelândia', childIds: [] }, 42: { id: 42, title: 'Vanuatu', childIds: [] }, 43: { id: 43, title: 'Lua', childIds: [44, 45, 46] }, 44: { id: 44, title: 'Rheita', childIds: [] }, 45: { id: 45, title: 'Piccolomini', childIds: [] }, 46: { id: 46, title: 'Tycho', childIds: [] }, 47: { id: 47, title: 'Marte', childIds: [48, 49] }, 48: { id: 48, title: 'Cidade do Milho', childIds: [] }, 49: { id: 49, title: 'Monte Verde', childIds: [] } };
Agora que o state está “plano” (também conhecido como “normalizado”), atualizar itens aninhados fica mais fácil.
Para remover um lugar agora, você só precisa atualizar dois níveis de state:
- A versão atualizada de seu lugar pai deve excluir o ID removido de seu array
childIds
. - A versão atualizada do objeto “tabela” raiz deve incluir a versão atualizada do lugar pai.
Aqui está um exemplo de como você poderia fazer isso:
import { useState } from 'react'; import { initialTravelPlan } from './places.js'; export default function TravelPlan() { const [plan, setPlan] = useState(initialTravelPlan); function handleComplete(parentId, childId) { const parent = plan[parentId]; // Cria uma nova versão do lugar pai // que não inclui o ID deste filho. const nextParent = { ...parent, childIds: parent.childIds .filter(id => id !== childId) }; // Atualiza o objeto de *state* raiz... setPlan({ ...plan, // ...para que tenha o pai atualizado. [parentId]: nextParent }); } const root = plan[0]; const planetIds = root.childIds; return ( <> <h2>Lugares para visitar</h2> <ol> {planetIds.map(id => ( <PlaceTree key={id} id={id} parentId={0} placesById={plan} onComplete={handleComplete} /> ))} </ol> </> ); } function PlaceTree({ id, parentId, placesById, onComplete }) { const place = placesById[id]; const childIds = place.childIds; return ( <li> {place.title} <button onClick={() => { onComplete(parentId, id); }}> Completar </button> {childIds.length > 0 && <ol> {childIds.map(childId => ( <PlaceTree key={childId} id={childId} parentId={id} placesById={placesById} onComplete={onComplete} /> ))} </ol> } </li> ); }
Você pode aninhar o state o quanto quiser, mas torná-lo “plano” pode resolver inúmeros problemas. Isso torna o state mais fácil de atualizar, e ajuda a garantir que você não tenha duplicação em diferentes partes de um objeto aninhado.
Deep Dive
Idealmente, você também removeria os itens excluídos (e seus filhos!) do objeto “tabela” para melhorar o uso da memória. Esta versão faz isso. Ele também usa Immer para tornar a lógica de atualização mais concisa.
import { useImmer } from 'use-immer'; import { initialTravelPlan } from './places.js'; export default function TravelPlan() { const [plan, updatePlan] = useImmer(initialTravelPlan); function handleComplete(parentId, childId) { updatePlan(draft => { // Remove os IDs dos filhos do lugar pai. const parent = draft[parentId]; parent.childIds = parent.childIds .filter(id => id !== childId); // Remove o lugar e todos os seus filhos. deleteAllChildren(childId); function deleteAllChildren(id) { const place = draft[id]; place.childIds.forEach(deleteAllChildren); delete draft[id]; } }); } const root = plan[0]; const planetIds = root.childIds; return ( <> <h2>Lugares para visitar</h2> <ol> {planetIds.map(id => ( <PlaceTree key={id} id={id} parentId={0} placesById={plan} onComplete={handleComplete} /> ))} </ol> </> ); } function PlaceTree({ id, parentId, placesById, onComplete }) { const place = placesById[id]; const childIds = place.childIds; return ( <li> {place.title} <button onClick={() => { onComplete(parentId, id); }}> Completar </button> {childIds.length > 0 && <ol> {childIds.map(childId => ( <PlaceTree key={childId} id={childId} parentId={id} placesById={placesById} onComplete={onComplete} /> ))} </ol> } </li> ); }
Por vezes, você também pode reduzir o aninhamento do state movendo parte do state aninhado para os componentes filhos. Isso funciona bem para o state de UI efêmero que não precisa ser armazenado, como saber se o mouse está passando sobre um item.
Recap
- Se duas variáveis de state sempre são atualizadas juntas, considere uní-las em uma.
- Escolha suas variáveis de state cuidadosamente para evitar criar estados “impossíveis”.
- Estruture seu state de uma maneira que reduza as chances de você cometer um erro ao atualizá-lo.
- Evite estados redundantes e duplicados para que você não precise mantê-los sincronizados.
- Não coloque props dentro de estados a menos que você queira especificamente impedir atualizações.
- Para padrões de UI como seleção, mantenha o ID ou o índice no state em vez do objeto em si.
- Se atualizar o state profundamente aninhado for complicado, tente achatá-lo.
Challenge 1 of 4: Corrija um componente que não está sendo atualizado
Este componente Clock
recebe duas props: color
e time
. Quando você seleciona uma cor diferente na caixa de seleção, o componente Clock
recebe uma prop color
diferente de seu componente pai. No entanto, por algum motivo, a cor exibida não é atualizada. Por quê? Corrija o problema.
import { useState } from 'react'; export default function Clock(props) { const [color, setColor] = useState(props.color); return ( <h1 style={{ color: color }}> {props.time} </h1> ); }