useReducer do react js
Como funciona o useReducer do React? Guia completo com exemplos práticos
Você já precisou controlar vários estados em um componente React e sentiu que o código estava ficando confuso? O useReducer pode ajudar a organizar essa situação. Neste guia, você vai aprender o que ele é, como funciona por trás dos panos, quando utilizá-lo e como criar exemplos práticos passo a passo.
- O que é o useReducer e qual problema ele resolve.
- A diferença entre useState e useReducer.
- O que são reducer, state, action e dispatch.
- Como criar um contador e uma lista de tarefas.
- Como evitar os erros mais comuns.
1. O que é o useReducer no React?
O useReducer é um Hook do React usado para controlar o estado de um componente. Ele é especialmente útil quando as mudanças desse estado precisam seguir regras bem organizadas.
Mas o que significa estado? Imagine que você tem um contador na tela. O número mostrado é uma informação que pode mudar. Essa informação é um estado.
O React oferece o useState para controlar estados individuais. Já o useReducer permite reunir as regras de atualização em uma função chamada reducer.
Você faz um pedido ao atendente. O atendente verifica as regras e atualiza o pedido. No React, você envia uma ação, o reducer decide qual será o novo estado e o React atualiza a interface quando necessário.
A ideia principal é esta: em vez de espalhar as regras de atualização pelo componente, você pode colocá-las em um lugar específico.
2. Para que serve o useReducer?
Ele serve para organizar mudanças de estado. É particularmente interessante quando existem várias ações diferentes que alteram os mesmos dados.
Veja dois exemplos simples:
Exemplo 1: contador
Um botão aumenta o número, outro diminui e um terceiro volta o número para zero. O reducer reúne as regras dessas três ações.
Exemplo 2: lista de tarefas
Você pode adicionar uma tarefa, remover uma tarefa ou marcar uma tarefa como concluída. O reducer centraliza as regras responsáveis por essas mudanças.
Isso não significa que você deva usar useReducer em todos os componentes. Para um estado simples, o useState costuma ser suficiente.
3. Entenda os quatro conceitos fundamentais
Antes de escrever código, precisamos entender quatro palavras que aparecem em quase todo exemplo de useReducer.
3.1. State: o estado atual
state é o nome normalmente usado para representar os dados atuais do componente.
Se o contador mostra 5, o estado atual do contador é 5. Se uma lista contém três tarefas, o estado pode ser um array com essas três tarefas.
3.2. Action: o que queremos fazer
Uma action descreve uma mudança que queremos solicitar. Ela costuma ser um objeto com uma propriedade chamada type.
Por exemplo:
const action = {
type: "incrementar"
};
Esse objeto informa que queremos incrementar o contador. Ele descreve a intenção, mas não altera o estado sozinho.
3.3. Reducer: a função que decide a mudança
O reducer é uma função que recebe o estado atual e uma ação. Com base nesses dois valores, ela devolve o próximo estado.
Pense nele como um funcionário que conhece as regras. Se recebe uma ação de incrementar, devolve um número maior. Se recebe uma ação de zerar, devolve zero.
3.4. Dispatch: o mensageiro
dispatch é a função que usamos para enviar uma ação ao reducer.
Por exemplo, quando alguém clica no botão de incrementar, chamamos dispatch com a ação correspondente.
- State: como as coisas estão agora.
- Action: o que queremos fazer.
- Reducer: a regra que calcula a mudança.
- Dispatch: envia o pedido de mudança.
4. Como o useReducer funciona por trás dos panos?
O funcionamento pode ser entendido como uma sequência de cinco etapas. Vamos imaginar que um contador mostra o número zero e o usuário clica em um botão para aumentar o valor.
Etapa 1: o usuário clica
O React executa a função associada ao evento de clique.
Etapa 2: o componente envia uma ação
A função chama dispatch e envia uma ação com o tipo "incrementar".
Etapa 3: o reducer recebe os dados
O React utiliza a função reducer com o estado atual e a ação enviada.
Etapa 4: o reducer devolve o novo estado
Se o valor atual é zero, a função devolve um. O reducer calcula o valor, mas não deve modificar diretamente o estado anterior.
Etapa 5: o React atualiza a interface
Quando o novo estado é diferente segundo as regras de comparação do React, o componente pode ser renderizado novamente para mostrar o valor atualizado.
Você não precisa alterar manualmente o texto na página. O React cuida da atualização da interface usando o estado retornado pelo reducer.
5. Como utilizar o useReducer: primeiro exemplo completo
Objetivo: criar um contador
Vamos construir um contador com três botões: aumentar, diminuir e zerar. Esse exemplo ensina a estrutura básica do useReducer.
Passo 1: importe o useReducer
O useReducer é um Hook fornecido pelo React. Precisamos importá-lo para utilizá-lo no componente.
import { useReducer } from "react";
Passo 2: crie a função reducer
A função abaixo recebe o valor atual do contador e uma ação. O switch verifica o tipo de ação para decidir qual valor deve retornar.
function contadorReducer(state, action) {
switch (action.type) {
case "incrementar":
return state + 1;
case "decrementar":
return state - 1;
case "zerar":
return 0;
default:
return state;
}
}
Entenda cada parte:
contadorReduceré o nome da função.staterepresenta o valor atual.actioncontém a solicitação de mudança.switchverifica qual ação foi enviada.returndevolve o próximo estado.defaultmantém o estado se a ação não for reconhecida.
Passo 3: conecte o reducer ao componente
Agora precisamos chamar o Hook. Ele devolve um array com dois elementos: o estado atual e a função dispatch.
const [state, dispatch] = useReducer(
contadorReducer,
0
);
O primeiro argumento é a função reducer. O segundo é o estado inicial, que neste exemplo é zero.
O primeiro elemento do array é o estado. O segundo é a função que envia ações.
Passo 4: envie ações pelos botões
Para aumentar o contador, enviamos uma ação com o tipo "incrementar". Para diminuir, usamos "decrementar".
import { useReducer } from "react";
function contadorReducer(state, action) {
switch (action.type) {
case "incrementar":
return state + 1;
case "decrementar":
return state - 1;
case "zerar":
return 0;
default:
return state;
}
}
export default function Contador() {
const [state, dispatch] = useReducer(
contadorReducer,
0
);
return (
<div>
<h2>Contador: {state}</h2>
<button
onClick={() => dispatch({
type: "incrementar"
})}
>
Aumentar
</button>
<button
onClick={() => dispatch({
type: "decrementar"
})}
>
Diminuir
</button>
<button
onClick={() => dispatch({
type: "zerar"
})}
>
Zerar
</button>
</div>
);
}
Qual será o resultado?
Inicialmente, a tela mostrará Contador: 0. Se você clicar em Aumentar, o valor passará para 1. Se clicar em Diminuir, ele voltará para 0. Se clicar em Zerar, o valor será definido como zero.
O exemplo funciona porque cada botão envia uma ação, o reducer interpreta essa ação e retorna o estado que o React utilizará na renderização.
6. Segundo exemplo: lista de tarefas com useReducer
Agora vamos usar um estado um pouco mais complexo. Em vez de um único número, teremos uma lista de objetos.
Cada tarefa terá um identificador, um nome e uma informação que indica se ela foi concluída.
Passo 1: entenda o formato dos dados
const tarefa = {
id: 1,
texto: "Estudar React",
concluida: false
};
O campo id identifica a tarefa. O campo texto contém seu nome. O campo concluida informa se ela foi finalizada.
Passo 2: crie o reducer
Teremos três ações: adicionar, alternar a conclusão e remover uma tarefa. Observe que usamos métodos como map e filter para criar novos arrays, sem alterar diretamente o array anterior.
function tarefasReducer(state, action) {
switch (action.type) {
case "adicionar":
return [
...state,
{
id: action.id,
texto: action.texto,
concluida: false
}
];
case "alternar":
return state.map((tarefa) =>
tarefa.id === action.id
? {
...tarefa,
concluida: !tarefa.concluida
}
: tarefa
);
case "remover":
return state.filter(
(tarefa) => tarefa.id !== action.id
);
default:
return state;
}
}
Como as três ações funcionam?
- Adicionar: o operador
...copia os itens anteriores para um novo array, e a nova tarefa é colocada no final. - Alternar: o
mappercorre a lista. Na tarefa escolhida, cria um novo objeto com o valor deconcluidainvertido. - Remover: o
filtercria um novo array sem a tarefa cujo identificador corresponde à ação.
Passo 3: crie o componente completo
Vamos adicionar um campo de texto, um botão para cadastrar a tarefa e uma lista com opções para concluir ou remover cada item.
import { useReducer, useState } from "react";
function tarefasReducer(state, action) {
switch (action.type) {
case "adicionar":
return [
...state,
{
id: action.id,
texto: action.texto,
concluida: false
}
];
case "alternar":
return state.map((tarefa) =>
tarefa.id === action.id
? {
...tarefa,
concluida: !tarefa.concluida
}
: tarefa
);
case "remover":
return state.filter(
(tarefa) => tarefa.id !== action.id
);
default:
return state;
}
}
export default function Tarefas() {
const [tarefas, dispatch] = useReducer(
tarefasReducer,
[]
);
const [texto, setTexto] = useState("");
function adicionarTarefa(event) {
event.preventDefault();
const textoLimpo = texto.trim();
if (!textoLimpo) return;
dispatch({
type: "adicionar",
id: crypto.randomUUID(),
texto: textoLimpo
});
setTexto("");
}
return (
<section>
<h2>Minha lista de tarefas</h2>
<form onSubmit={adicionarTarefa}>
<input
value={texto}
onChange={(event) =>
setTexto(event.target.value)
}
placeholder="Digite uma tarefa"
/>
<button type="submit">
Adicionar
</button>
</form>
<ul>
{tarefas.map((tarefa) => (
<li key={tarefa.id}>
<span
style={{
textDecoration: tarefa.concluida
? "line-through"
: "none"
}}
>
{tarefa.texto}
</span>
<button
type="button"
onClick={() => dispatch({
type: "alternar",
id: tarefa.id
})}
>
{tarefa.concluida
? "Reabrir"
: "Concluir"}
</button>
<button
type="button"
onClick={() => dispatch({
type: "remover",
id: tarefa.id
})}
>
Remover
</button>
</li>
))}
</ul>
</section>
);
}
crypto.randomUUID() gera um identificador único em ambientes compatíveis, normalmente em contextos seguros, como HTTPS e localhost. Se o ambiente não oferecer esse recurso, utilize uma estratégia de identificação apropriada para o projeto.
Passo 4: entenda por que usamos dois Hooks
Você deve ter percebido que o exemplo utiliza tanto useReducer quanto useState.
O useReducer controla a lista de tarefas. Já o useState controla o texto digitado no campo.
Isso é perfeitamente válido. Não precisamos utilizar o mesmo Hook para todos os estados. Escolhemos a ferramenta de acordo com o problema.
Resultado esperado
- Digite Estudar React no campo.
- Clique em Adicionar.
- A tarefa aparecerá na lista.
- Clique em Concluir para riscar o texto.
- Clique em Reabrir para voltar ao estado anterior.
- Clique em Remover para retirar a tarefa da lista.
7. Qual é a diferença entre useState e useReducer?
Os dois Hooks controlam estados. A principal diferença está na maneira como organizamos as atualizações.
| Característica | useState | useReducer |
|---|---|---|
| Como atualiza | Com uma função de atualização ou um novo valor. | Envia uma ação para um reducer. |
| Estados simples | Geralmente é a opção mais direta. | Pode ser desnecessariamente complexo. |
| Várias regras de mudança | Pode funcionar bem, dependendo do caso. | Ajuda a reunir as regras em uma função. |
| Organização | As atualizações podem ficar espalhadas pelo componente. | As regras ficam concentradas no reducer. |
| Uso típico | Campo de texto, modal aberto ou contador simples. | Formulários complexos, listas e vários tipos de ação. |
Exemplo 1: usando useState
const [contador, setContador] =
useState(0);
setContador((valorAtual) => valorAtual + 1);
Para uma atualização tão simples, o useState é direto e fácil de entender.
Exemplo 2: usando useReducer
const [contador, dispatch] =
useReducer(contadorReducer, 0);
dispatch({ type: "incrementar" });
Aqui, a atualização depende de uma regra definida em contadorReducer. Isso pode ficar mais organizado quando existem muitas ações.
Se você só precisa guardar um valor e alterá-lo de maneira simples, comece com useState. Se há muitas ações relacionadas ao mesmo estado, considere useReducer.
8. O reducer precisa ser puro: o que isso significa?
Uma função pura é uma função que calcula seu resultado usando os dados recebidos, sem causar efeitos colaterais inesperados.
No caso de um reducer, isso significa que ele deve calcular o próximo estado sem modificar diretamente o estado anterior e sem realizar tarefas externas, como chamadas à API.
Exemplo correto
function reducer(state, action) {
if (action.type === "incrementar") {
return state + 1;
}
return state;
}
A função recebe um valor, verifica a ação e devolve um resultado. Ela não altera diretamente o valor recebido.
Exemplo incorreto
function reducer(state, action) {
if (action.type === "adicionar") {
state.push(action.item);
return state;
}
return state;
}
O problema é que push modifica o próprio array original. Isso pode causar comportamentos inesperados porque o React depende da identidade dos valores para identificar atualizações.
Uma alternativa é retornar um novo array:
return [...state, action.item];
O React pode executar reducers mais de uma vez em ambientes de desenvolvimento com verificações adicionais, como o Strict Mode. Por isso, evite efeitos colaterais dentro do reducer.
9. Erros comuns ao utilizar useReducer
Erro 1: esquecer de retornar o novo estado
Um reducer precisa devolver o estado seguinte. Se um caminho de execução retornar undefined, você pode provocar erros na aplicação.
Solução: garanta que todos os casos retornem um estado válido e utilize um caso default apropriado.
Erro 2: enviar uma ação sem o tipo esperado
dispatch({ type: "somar" });
Se o reducer só reconhece "incrementar", "decrementar" e "zerar", a ação "somar" não executará nenhuma dessas regras.
Solução: mantenha os nomes das ações consistentes entre o reducer e o componente.
Erro 3: modificar o estado diretamente
Alterar diretamente um objeto ou array recebido pode prejudicar a previsibilidade das atualizações.
Solução: use operações que produzam novos arrays e objetos, como map, filter e o operador de espalhamento ....
Erro 4: colocar chamadas de API dentro do reducer
Um reducer não é o lugar apropriado para buscar dados na internet, iniciar temporizadores ou executar outras tarefas externas.
Solução: execute essas tarefas em manipuladores de eventos, Effects ou outras partes apropriadas da aplicação. Depois, envie uma ação com o resultado necessário.
Erro 5: utilizar useReducer para tudo
Se você tem apenas um campo de texto, um estado simples com useState geralmente resolve o problema com menos código.
Solução: escolha o Hook de acordo com a complexidade real do estado.
10. Boas práticas para escrever reducers
- Use nomes claros: prefira ações como
"adicionar","remover"e"alternar". - Centralize regras relacionadas: mantenha no reducer as decisões que modificam o estado controlado por ele.
- Não modifique o estado anterior: retorne novos arrays ou objetos quando necessário.
- Mantenha a função previsível: o resultado deve depender do estado e da ação recebidos.
- Trate ações desconhecidas: retornar o estado atual no caso padrão é uma estratégia comum.
- Evite complexidade desnecessária: um reducer pequeno e bem organizado é mais fácil de manter.
- Separe responsabilidades: não misture regras de estado com chamadas à API dentro da função reducer.
Em projetos maiores, também é comum organizar o reducer em um arquivo separado, principalmente quando ele possui muitas regras.
11. Como utilizar useReducer em formulários
Um formulário pode possuir vários dados: nome, e-mail, senha, mensagens de erro e estado de envio. Quando essas informações têm muitas regras de atualização relacionadas, o useReducer pode ajudar.
Por exemplo, podemos definir ações como "alterar_nome", "alterar_email" e "limpar".
const estadoInicial = {
nome: "",
email: ""
};
function formularioReducer(state, action) {
switch (action.type) {
case "alterar_nome":
return {
...state,
nome: action.valor
};
case "alterar_email":
return {
...state,
email: action.valor
};
case "limpar":
return estadoInicial;
default:
return state;
}
}
O operador ...
...state, que copia as propriedades existentes para um novo objeto antes de alterar o campo desejado.
Esse exemplo mostra como manter os dados relacionados em um único estado. Em um formulário real, você também precisaria conectar os campos ao estado e implementar validação e envio de dados.
12. Exercício prático: crie seu próprio reducer
Agora é sua vez de praticar. Crie um pequeno sistema de estoque que controle a quantidade de um produto.
- O estoque deve começar com 10 unidades.
- A ação
"vender"deve diminuir uma unidade. - A ação
"repor"deve aumentar uma unidade. - A ação
"reiniciar"deve voltar o estoque para 10. - O reducer não deve permitir que o estoque fique negativo.
Tente completar este código
function estoqueReducer(state, action) {
switch (action.type) {
case "vender":
// Não permita estoque negativo.
return /* complete aqui */;
case "repor":
return /* complete aqui */;
case "reiniciar":
return 10;
default:
return state;
}
}
Resposta para conferir
Tente resolver sozinho antes de consultar a solução. Depois, compare seu código com o exemplo abaixo.
function estoqueReducer(state, action) {
switch (action.type) {
case "vender":
return Math.max(0, state - 1);
case "repor":
return state + 1;
case "reiniciar":
return 10;
default:
return state;
}
}
A função Math.max(0, state - 1) devolve o maior valor entre zero e o estoque após a venda. Assim, a quantidade nunca fica negativa.
13. Resumo: o que você precisa lembrar
- useReducer: Hook que organiza atualizações de estado por meio de uma função reducer.
- State: os dados atuais.
- Action: a descrição da mudança desejada.
- Reducer: a função que calcula o próximo estado.
- Dispatch: a função que envia uma ação.
- Imutabilidade: não modificar diretamente o estado anterior.
- useState: continua sendo uma ótima opção para estados simples.
A melhor maneira de aprender é praticar com exemplos pequenos. Comece pelo contador, depois tente a lista de tarefas e, por último, crie seu próprio reducer para controlar outro tipo de informação.
14. Conclusão
O useReducer é uma ferramenta importante do React porque permite organizar as regras que controlam um estado. Em vez de espalhar várias decisões pelo componente, você pode reunir essas regras em uma função que recebe o estado atual e uma ação.
Você aprendeu a criar reducers, enviar ações com dispatch, atualizar contadores, manipular listas e evitar erros relacionados à imutabilidade.
Lembre-se: não existe necessidade de utilizar useReducer em todos os componentes. O objetivo é escolher a ferramenta que deixa seu código mais claro, previsível e fácil de manter.
Conteúdo educativo sobre React JS, useReducer, gerenciamento de estado e boas práticas de desenvolvimento.
.png)
Comentários
Postar um comentário