useReducer do react js


 
REACT JS · GUIA PARA INICIANTES

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 você vai aprender:
  • 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.

Imagine uma lanchonete.

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:

Exemplo de action
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.

Memorize desta maneira:
  • 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.

O detalhe importante:

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.

Contador.jsx — importação
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.

Contador.jsx — reducer
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.
  • state representa o valor atual.
  • action contém a solicitação de mudança.
  • switch verifica qual ação foi enviada.
  • return devolve o próximo estado.
  • default manté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.

Contador.jsx — estado e 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".

Contador.jsx — componente completo
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

Formato de uma tarefa
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.

Tarefas.jsx — reducer
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 map percorre a lista. Na tarefa escolhida, cria um novo objeto com o valor de concluida invertido.
  • Remover: o filter cria 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.

Tarefas.jsx — exemplo completo
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>
  );
}
Observação sobre o identificador:

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

  1. Digite Estudar React no campo.
  2. Clique em Adicionar.
  3. A tarefa aparecerá na lista.
  4. Clique em Concluir para riscar o texto.
  5. Clique em Reabrir para voltar ao estado anterior.
  6. 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

Contador simples
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

Enviando uma ação
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.

Regra prática:

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

Criando um novo estado
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

Não faça isso com arrays
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:

Alternativa correta
return [...state, action.item];
Importante:

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

Exemplo de ação incompatível
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".

Exemplo simplificado de formulário
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 ...

não é utilizado aqui: a sintaxe correta é ...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.

Requisitos do exercício:
  1. O estoque deve começar com 10 unidades.
  2. A ação "vender" deve diminuir uma unidade.
  3. A ação "repor" deve aumentar uma unidade.
  4. A ação "reiniciar" deve voltar o estoque para 10.
  5. O reducer não deve permitir que o estoque fique negativo.

Tente completar este código

estoqueReducer.js
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.

Solução do exercício
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.


Comentários

Postagens mais visitadas deste blog

useMemo do React js

useEffect do React js

useState do React js