DEV Community

Cover image for Entendendo o useReducer do React
Lucas Ruy
Lucas Ruy

Posted on • Edited on

Entendendo o useReducer do React

Neste artigo vamos entender como o useReducer funciona e dar um exemplo da vida real de uso para ele, mas primeiro vamos entender o que é esse hook. Uma definição curta para o useReducer é que ele permite você utilizar um reducer em seu componente. Ele entra como uma boa opção quando surge a necessidade de lidar com dados ou lógicas mais complexas para manipular estado. Esse hook utiliza o padrão de reducers que foi popularizado com o redux, uma biblioteca de gerenciamento de estado muito famosa e que ainda é amplamente utilizada no mercado.

Pegando um contexto

Antes iniciarmos com o useReducer primeiro vamos entender o que seria um reducer. Um reducer é uma função que recebe o estado atual, uma ação e retorna um novo estado. Simples assim. Por convenção, geralmente os redutores, tem instruções switch para dizer como o estado será atualizado e retornar esse novo valor.

Para entender melhor essa ideia, podemos imaginar uma receita: você tem os ingredientes(o estado atual) e uma instrução(ação), o resultado é um prato preparado por você(novo estado).

Agora vamos ver como um reducer se parece:

const initialState = { count: 0 };

function reducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

No exemplo o reducer faz exatamente o que explicamos anteriormente, ele é uma função que recebe o estado atual(state) e uma ação(action) e retorna um novo estado dependendo de qual ação será executada. Nesse exemplo existem duas possibilidades de ação, aumentar o contador(INCREMENT) ou diminuir(DECREMENT). Então se o tipo de ação for INCREMENT o valor retornado será { count: 1 }, agora se for uma ação do tipo DECREMENT o valor retornado será { count: -1 } e assim sucessivamente.

Um ponto muito importante dos reducers é a sua imutabilidade, eles sempre devem retornar um novo estado ao invés de modificar o estado atual. Isso garante que o estado seja imutável e pode ajudar a evitar bugs.

Como o "useReducer" funciona

Para entender o hook useReducer precisamos ter atenção as três principais partes do hook: a função redutora, a inicialização do estado e o despachador de ações. Vejamos um a um:

  • Função redutora: é a função que diz como o estado vai ser atualizado quando receber uma ação. Essa função recebe dois parâmetros, o estado atual e a ação que deve ser executada, a partir disso um novo estado é retornado.
  • Inicialização do estado: será o seu estado, é um objeto consumido pela sua função redutora.
  • Despachador de ações: é a função que recebe um objeto por parâmetro, esse objeto é a ação que contém as instruções necessárias para a função redutora atualizar seu estado.

Agora vamos para a sintaxe:

const [state, dispatch] = useReducer(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode

Aqui temos o seguinte:

  • state: estado atual.
  • dispatch: despachador de ações.
  • reducer: função redutora.
  • initialState: estado inicial, um objeto literal.

Exemplo da vida real

Para entender qual é a aplicabilidade do useReducer partiremos para um exemplo bem comum. Imagine que você precisa criar um carrinho de compras que pode ter as seguintes funcionalidades, adicionar, remover e limpar os itens.

Começaremos definindo nosso reducer em um arquivo chamado reducer.js :

// reducer.js
export const initialState = {
  items: [],
  totalAmount: 0
}

export function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      const updatedItems = [...state.items, action.item];
      const updatedTotalAmount = state.totalAmount + action.item.price;
      return {
        ...state,
        items: updatedItems,
        totalAmount: updatedTotalAmount,
      };
    case 'REMOVE_ITEM':
      const filteredItems = state.items.filter(item => item.id !== action.id);
      const itemToRemove = state.items.find(item => item.id === action.id);
      const decreasedTotalAmount = state.totalAmount - itemToRemove.price;
      return {
        ...state,
        items: filteredItems,
        totalAmount: decreasedTotalAmount,
      };
    case 'CLEAR_CART':
      return initialState;
    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

É importante notar que o objeto state nunca é modificado e sim utilizado como base para realizar operações futuras. Trabalhando dessa forma podemos garantir a imutabilidade, o que é uma característica de um reducer.

Agora precisamos criar nosso componente e utilizar o useReducer dentro dele:

// ShoppingCart.js
import { useReducer } from "react";

import { cartReducer, initialState } from "./shopping-cart.reducer";

export const ShoppingCart = () => {
  const [cart, dispatch] = useReducer(cartReducer, initialState);

  const hasCartItems = cart.items.length > 0;

  const addItemHandler = (item) => {
    dispatch({ type: "ADD_ITEM", item });
  };

  const removeItemHandler = (id) => {
    dispatch({ type: "REMOVE_ITEM", id });
  };

  const clearCartHandler = () => {
    dispatch({ type: "CLEAR_CART" });
  };

  return (
    <div>
      <h2>Meu carrinho</h2>
      {hasCartItems && (
        <ul>
          {cart.items.map((item) => (
            <li key={item.id}>
              {item.name} - ${item.price}
              <button onClick={() => removeItemHandler(item.id)}>
                Remover
              </button>
            </li>
          ))}
        </ul>
      )}
      <div>Total: ${cart.totalAmount}</div>
      <button onClick={clearCartHandler}>Limpar Carrinho</button>
      <button
        onClick={() =>
          addItemHandler({ id: "shoes-1", name: "Tênis", price: 99 })
        }
      >
        Adicionar "Tênis" ao carrinho
      </button>
      <button
        onClick={() =>
          addItemHandler({ id: "shirt-1", name: "Camiseta", price: 39 })
        }
      >
        Adicionar "Camiseta" ao carrinho
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

No arquivo reducer.js, definimos o estado inicial do carrinho e quais ações podem ser aplicadas para atualizá-lo. As ações disponíveis são ADD_ITEM, REMOVE_ITEM e CLEAR_CART. Cada vez que uma dessas ações é executada, uma cópia do estado atual é criada, atualizada conforme a ação, e um novo estado atualizado é retornado.

Já no componente ShoppingCart, utilizamos o useReducer para gerenciar o estado do carrinho. No componente, temos três funções, cada uma responsável por uma ação específica. Essas funções despacham ações para o nosso reducer, que se encarrega de atualizar o estado e retorná-lo. O componente ShoppingCart renderiza os itens do carrinho, o valor total e os botões de ação para adicionar, remover e limpar o carrinho.

Conclusão

Neste artigo aprendemos como o hook useReducer funciona e também um pouco sobre o conceito por trás dele. Também entendemos como ele pode ser uma ferramenta poderosa, que facilita o gerenciamento de estados mais complexos em seus componentes, principalmente quando a lógica de atualização do estado precisa de várias ações.

Código do exemplo neste sandbox.

Espero que este artigo tenha ajudado você de alguma forma a entender melhor o useReducer e como ele pode ser aplicado em casos reais para gerenciar estados complexos. Se você gostou dessa leitura e ficou interessado em se aprofundar ainda mais nesse assunto, recomendo as leituras a seguir, apenas em inglês:

Por hoje é isso e obrigado por ler até aqui!

Top comments (0)