Gerenciamento de estado global com useReducer e Context API


O uso do useReducer em conjunto com a Context API do React é preferível ao useState para gerenciar o estado global, especialmente quando há o risco de perder o estado do contexto. A razão principal reside na forma como o useReducer manipula as atualizações do estado, o que evita o problema de o desenvolvedor não utilizar o spread operator para copiar os valores do estado anterior e, assim, não perder o estado global do contexto.

1. Definindo ações

Começamos definindo as ações que serão despachadas pelo redutor. Criaremos um arquivo chamado actions.js:

// actions.js
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const RESET = 'RESET';
 
export { INCREMENT, DECREMENT, RESET };

2. Criando a função redutora

Agora, implementaremos a função redutora no arquivo reducer.js. Ele atualizará o estado de acordo com o tipo da ação:

// reducer.js
import { INCREMENT, DECREMENT, RESET } from './actions';
 
const reducer = (state, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    case RESET:
      return { count: 0 };
    default:
      return state;
  }
};
 
export default reducer;

3. Criando o contexto

Vamos criar o contexto que armazenará o estado global e fornecerá funções específicas do dispatch para cada tipo de ação. Isso será feito no arquivo Context.js:

// Context.js
import React, { createContext, useReducer } from 'react';
import reducer from './reducer';
 
const initialState = { count: 0 };
const AppContext = createContext();
 
const AppProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
 
  const increment = () => {
    dispatch({ type: INCREMENT });
  };
 
  const decrement = () => {
    dispatch({ type: DECREMENT });
  };
 
  const reset = () => {
    dispatch({ type: RESET });
  };
 
  return (
    <AppContext.Provider value={{ state, increment, decrement, reset }}>
      {children}
    </AppContext.Provider>
  );
};
 
export { AppContext, AppProvider };

4. Utilizando o contexto em componentes

Agora, podemos usar esse contexto em nossos componentes. Abaixo, um exemplo simples com um componente de contagem:

// Counter.js
import React, { useContext } from 'react';
import { AppContext } from './Context';
 
const Counter = () => {
  const { state, increment, decrement, reset } = useContext(AppContext);
 
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};
 
export default Counter;

5. Integrando com o componente principal

Integramos nosso contexto global no componente principal (App.js), onde o componente Counter é renderizado:

// App.js
import React from 'react';
import { AppProvider } from './Context';
import Counter from './Counter';
 
const App = () => {
  return (
    <AppProvider>
      <div>
        <h1>Counter App</h1>
        <Counter />
      </div>
    </AppProvider>
  );
};
 
export default App;

A principal vantagem do uso de useReducer é evidenciada quando lidamos com estados mais complexos, pois ele garante a imutabilidade do estado. Enquanto o useState pode levar a problemas se o desenvolvedor esquecer de copiar o estado anterior ao realizar uma atualização, já o useReducer lida naturalmente com essa situação, uma vez que é necessário fornecer o novo estado com base no estado anterior. Isso minimiza os riscos de perder o estado global do contexto e contribui para um gerenciamento de estado mais robusto e previsível.

Referências