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;
}
}
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);
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;
}
}
É 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>
);
};
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)