Identificar o estado essencial
Antes de acrescentar um setter, identifica o facto que precisa de memória entre interações. A lista de registos e o id selecionado são factos distintos. O título selecionado pode ser obtido da lista em cada render e não exige outra cópia de estado.
O mesmo vale para uma soma que é sempre derivada dos preços atuais. Se guardares o resultado separadamente, cada edição, remoção e substituição terá de atualizar também essa cópia. A estrutura deve reduzir essas obrigações.
Valores derivados podem ser constantes locais; uma eventual otimização de cálculo será uma decisão posterior apoiada em medição. Não confundas evitar duplicação com impedir todo o estado local: um rascunho editável pode ser um facto intencionalmente diferente do valor confirmado.
Estados válidos e substituição explícita
Um processo que só pode estar em editing, saving ou saved fica mais claro com um status do que com booleanos independentes que permitem combinações contraditórias. As condições visuais podem ser derivadas desse status. Se duas coordenadas mudam sempre em conjunto, um objeto pode representar a relação, mas useState não faz merge automático de objetos.
Substituir {x: 1, y: 2} por {x: 3} perde y nesse novo valor. Para conservar y, constrói o objeto seguinte com os campos necessários. O estado da interface também não certifica um resultado externo: só deves representar confirmação do serviço quando houver evidência para isso. Neste exemplo, Save locally altera apenas a lista em memória.
Atualizar os níveis que mudam
O handler save usa map para construir um array novo. Para o registo alterado, cria {...record, title}; para os restantes, conserva as referências porque não lhes muda propriedades. Copiar um array com spread não copia os seus objetos.
Se fizeres next = [...records] e depois next[0].title = value, o objeto antigo também foi alterado. Num estado com address.city, copia address e o objeto que o contém para substituir city sem escrever no snapshot anterior. Não é necessário clonar indiscriminadamente todos os objetos da aplicação.
O objetivo é preservar os dados existentes e construir o caminho de referências correspondente à alteração. Isto facilita perceber que componente recebeu dados novos e evita efeitos sobre consumidores do objeto anterior.
Selecionar por identidade
Guardar selectedItem como objeto pode duplicar informação da lista. Depois de substituir esse item por uma versão editada, a seleção pode continuar a apontar para a versão antiga. Guardar selectedId e procurar o objeto atual elimina essa duplicação.
Define também o caso em que a pesquisa não encontra nada: a lista pode estar vazia ou o registo pode ter sido removido. O exemplo mostra No selected record em vez de ler uma propriedade de undefined. Escolher automaticamente outro registo, limpar a seleção ou conservar uma indicação de remoção são políticas diferentes.
Não deixes uma suposição escondida de que find terá sempre resultado decidir o comportamento do produto.
Posição, tipo e continuidade do estado
React associa estado à instância na árvore renderizada. Duas ocorrências de Counter em posições diferentes podem ter estados independentes apesar de usarem a mesma função. O mesmo tipo na mesma posição mantém normalmente o estado quando mudam props; a posição no ficheiro JSX não é o critério.
Se Editor for removido e substituído por um parágrafo, o estado dessa instância é destruído. Mostrar Editor mais tarde cria uma instância nova. Para conservar rascunhos de componentes removidos, seria necessário outro desenho, como estado num proprietário que permaneça montado.
A análise deve seguir a árvore e a fronteira de propriedade, em vez de contar linhas de JSX ou confiar no nome de uma função.
Keys estáveis e resets deliberados
Uma lista que pode mudar de ordem precisa de identidade por item. Um índice representa a posição atual e pode ligar o estado local ao registo errado depois de uma reordenação. Usa um id estável e único entre irmãos. key é interpretada por React; passa record ou rowId separadamente se o componente precisar de ler esses dados.
Uma key aleatória em cada render força identidade nova e pode perder texto e foco. No exemplo, a key de Editor é selected.id: mudar de registo faz um reset deliberado de toda essa subárvore. Não é uma técnica para sincronizar todos os campos em cada tecla.
Mantém a key quando a instância deve continuar e muda-a quando a política exige começar de novo.
Inicialização e política de rascunho
useState(record.title) usa o título para inicializar o rascunho da instância. Novas props para a mesma instância não repõem esse estado automaticamente. Isso pode proteger uma edição em curso ou causar um desvio, conforme o contrato.
O exemplo usa initialRecords para deixar explícito que novas props não sincronizam a lista local. Ao trocar de id, o reset é deliberado e perde o rascunho anterior. Numa aplicação real, essa política pode exigir confirmação antes da troca; o excerto não implementa esse fluxo.
Para avaliar uma proposta, distingue atualização normal do mesmo registo, troca de identidade, remoção da árvore e confirmação remota. Cada situação exige uma decisão de conservação ou substituição de dados.
import {useState} from 'react';
// Parent supplies unique, stable record IDs and string titles.
function Editor({record, onSave}) {
const [draft, setDraft] = useState(record.title);
return <section>
<label>Title <input value={draft}
onChange={e => setDraft(e.target.value)} /></label>
<button type="button" onClick={() => onSave(record.id, draft)}>Save locally</button>
</section>;
}
export default function RecordReview({initialRecords}) {
const [records, setRecords] = useState(initialRecords);
const [selectedId, setSelectedId] = useState(initialRecords[0]?.id ?? null);
const selected = records.find(record => record.id === selectedId);
function save(id, title) {
setRecords(current => current.map(record =>
record.id === id ? {...record, title} : record));
}
return <>
<ul>{records.map(record => <li key={record.id}>
<button type="button" onClick={() => setSelectedId(record.id)}>{record.title}</button>
</li>)}</ul>
{selected ? <Editor key={selected.id} record={selected} onSave={save} />
: <p>No selected record</p>}
</>;
}O exemplo guarda a lista e selectedId, derivando o objeto selecionado. Guardar cria um objeto novo para o registo alterado. Mudar de id reinicializa Editor: o contrato descarta o rascunho anterior. initialRecords serve apenas para inicialização; não há sincronização com novas props nem persistência remota.
Armadilhas comuns
Copiar apenas o array e mutar um objeto partilhado; duplicar o item selecionado; usar índices numa lista reordenável; gerar keys no render; esperar que useState reinicialize sempre que uma prop muda.
Tópicos relacionados: Eventos, snapshots de estado e filas de atualização · Formulários, contratos DOM e acessibilidade · Reducers, contexto e estado partilhado
Mantém uma fonte para cada facto e uma identidade estável para cada instância que deve continuar. Copia os níveis alterados e usa mudanças de key apenas quando o reset corresponde à política do produto.
Referência: Essential state and contradictions · React 19.3.0; JSX