Transições como decisões explícitas
Um reducer recebe estado e ação e devolve o estado seguinte. A convenção de uma ação com type e dados adicionais ajuda a tornar a intenção legível, mas React não atribui significado comercial a palavras como archived ou closed. No exemplo, archived muda um booleano num registo local porque esse é o ramo implementado.
O reducer não fecha uma ligação nem envia uma confirmação. Ao rever uma ação, identifica a condição de entrada, o objeto que pode mudar e os campos que devem continuar. Usa props e handlers simples enquanto forem suficientes; introduzir um reducer deve clarificar transições que já existem, não inventar um sistema global desnecessário.
O resultado é o estado completo
O resultado devolvido pelo reducer substitui o estado anterior. Se o estado era {title, status} e um ramo devolve apenas {status: saved}, title não é recuperado por React. Inclui explicitamente os campos a preservar, por exemplo com spread, e copia os níveis alterados sem mutar os objetos anteriores.
Um reducer que escreve no próprio state e o devolve pode também interferir com a comparação de identidade usada para evitar trabalho. Não uses esse comportamento como mecanismo de atualização. O exemplo cria um array novo e um objeto novo para o registo selecionado, mantendo os restantes objetos intactos.
A revisão deve avaliar todos os ramos e os seus resultados, incluindo ações não reconhecidas.
Dispatch, inicialização e pureza
dispatch recebe a ação; não devolve o estado calculado nem reescreve o snapshot do handler em execução. A sua identidade é estável entre renders da instância, mas isso não torna os valores de estado mutáveis. Para inicialização através de uma função, useReducer(reducer, seed, buildInitial) usa o terceiro argumento como inicializador.
Escrever buildInitial(seed) no segundo argumento avalia a expressão quando o componente é chamado, mesmo que esse valor inicial já não seja usado. Reducer e inicializador devem ser puros. As verificações de desenvolvimento podem avaliá-los novamente para revelar mutações e efeitos; não devem disparar gravações ou mensagens externas em cada avaliação.
Distribuir estado e intenção pela árvore
O exemplo mantém useReducer em ReviewQueue e fornece rows e dispatch em dois contexts. QueueBody lê ambos porque apresenta os registos e desencadeia ações. Outro descendente poderia precisar apenas de leitura ou apenas de dispatch.
A separação clarifica dependências, mas não garante que nenhum componente volte a renderizar por outras razões. Contexto é uma forma de transportar valores pela árvore; não substitui a decisão sobre quem é proprietário do estado. O provider tem de envolver os consumidores relevantes.
Um consumidor fora dessa fronteira não recebe automaticamente os dados só por estar no mesmo ficheiro ou usar um nome parecido.
Provider mais próximo e fallback
useContext seleciona o provider correspondente mais próximo acima do componente. Um provider aninhado pode substituir o valor para a sua subárvore sem fundir automaticamente valores com o exterior. Se não houver provider correspondente acima, usa-se o default definido em createContext.
Um provider que fornece undefined continua a existir e o consumidor recebe undefined; o default não é uma normalização desse valor. Também não podes ler em Panel o provider que o próprio Panel só devolve à volta dos seus filhos: ele está abaixo dessa leitura.
No exemplo, null é um sinal deliberado de provider ausente e QueueBody comunica esse erro em vez de continuar com pressupostos inválidos.
Identidade do contexto e do valor
Provider e consumidor precisam de usar o mesmo objeto criado por createContext. Duas cópias de um módulo podem criar objetos diferentes com o mesmo nome; nomes iguais não os tornam intercambiáveis. Investiga esse limite quando um consumidor recebe fallback apesar de parecer existir um provider.
Depois distingue a identidade do contexto da identidade do seu value. Um novo objeto {user, login} em cada render compara como diferente e pode atualizar consumidores mesmo quando os dados subjacentes não mudaram. memo não impede uma atualização de contexto necessária.
Estabilizar objetos e callbacks pode ser útil como otimização, mas deve preservar as dependências corretas e ser avaliado segundo o trabalho observado.
Rever a operação completa
Começa pelo estado anterior e pela ação recebida. Calcula o resultado de cada ramo e verifica que os campos exigidos continuam presentes. Depois segue esse resultado até ao provider e ao consumidor correto.
Se a interface continua desatualizada, diferencia mutação do mesmo objeto, provider ausente, contexto duplicado e simples leitura de um snapshot antigo no handler. A resposta não é sempre adicionar mais estado ou outro provider. O exemplo é deliberadamente local: archived significa uma decisão guardada em memória nesta instância.
Uma integração real teria de representar pedidos, confirmação e falha separadamente, mantendo o reducer responsável pelo cálculo das transições em vez de executar esses efeitos.
import {createContext, useContext, useReducer} from 'react';
const RowsContext = createContext(null);
const DispatchContext = createContext(null);
function reducer(rows, action) {
switch (action.type) {
case 'archived':
return rows.map(row => row.id === action.id
? {...row, archived: true} : row);
default:
throw new Error('Unknown action');
}
}
function QueueBody() {
const rows = useContext(RowsContext);
const dispatch = useContext(DispatchContext);
if (rows === null || dispatch === null) throw new Error('Queue provider missing');
return <ul>{rows.map(row => <li key={row.id}>
{row.title}: {row.archived ? 'Archived locally' : 'Open'}
<button type="button" disabled={row.archived}
onClick={() => dispatch({type: 'archived', id: row.id})}>Archive locally</button>
</li>)}</ul>;
}
// initialRows: stable unique IDs, string titles, boolean archived.
export default function ReviewQueue({initialRows}) {
const [rows, dispatch] = useReducer(reducer, initialRows);
return <RowsContext value={rows}>
<DispatchContext value={dispatch}><QueueBody /></DispatchContext>
</RowsContext>;
}Archive locally envia uma ação com id. O reducer devolve um array novo e copia o objeto alterado; não faz pedidos de rede. Os contexts distribuem o estado e dispatch para QueueBody.
A sintaxe <Context value> usa React 19. O exemplo não demonstra autorização ou persistência remota.
Armadilhas comuns
Devolver um objeto parcial esperando merge; mutar o estado recebido; ler o retorno de dispatch como estado; colocar o provider abaixo do consumidor; criar contexts diferentes com o mesmo nome; confundir memoização com correção.
Tópicos relacionados: Estrutura do estado e identidade dos componentes · Effects, refs e sincronização externa · Desempenho, transições e decisões de renderização
O reducer interpreta uma ação e devolve o próximo estado sem efeitos. Contexto transporta um valor pela árvore usando identidade e proximidade do provider. Nenhum destes mecanismos confirma por si uma operação num serviço externo.
Referência: Reducer contract · React 19.3.0; JSX