O que Todo Dev Deve Saber sobre Hooks Avançados em React: useReducer, useContext e useImperativeHandle Já leu

useReducer: Gerenciamento de Estado Complexo O é o hook ideal quando seu estado tem múltiplas sub-valores ou a lógica de atualização é complexa. Diferente do , ele utiliza um padrão reducer similar ao Redux, onde ações disparadas modificam o estado de forma previsível. A estrutura consiste em três elementos: o estado atual, uma função reducer que processa ações, e a função dispatch para dispará-las. Considere um carrinho de compras onde você precisa adicionar produtos, remover e limpar o carrinho simultaneamente: Essa abordagem torna o código mais previsível e facilita testes, especialmente em aplicações com lógica complexa. useContext: Compartilhamento de Estado Global O elimina a necessidade de prop drilling ao criar um canal direto de comunicação entre componentes distantes na árvore. Combinado com , cria um sistema robusto de gerenciamento de estado. A implementação envolve criar um Context, um Provider que envolve a aplicação, e consumidores que acessam os valores. Veja um exemplo prático com tema da aplicação: Este padrão é

useReducer: Gerenciamento de Estado Complexo

O useReducer é o hook ideal quando seu estado tem múltiplas sub-valores ou a lógica de atualização é complexa. Diferente do useState, ele utiliza um padrão reducer similar ao Redux, onde ações disparadas modificam o estado de forma previsível.

A estrutura consiste em três elementos: o estado atual, uma função reducer que processa ações, e a função dispatch para dispará-las. Considere um carrinho de compras onde você precisa adicionar produtos, remover e limpar o carrinho simultaneamente:

import React, { useReducer } from 'react';

const initialState = { items: [], total: 0 };

function cartReducer(state, action) {
  switch(action.type) {
    case 'ADD_ITEM':
      return {
        items: [...state.items, action.payload],
        total: state.total + action.payload.price
      };
    case 'REMOVE_ITEM':
      return {
        items: state.items.filter((_, i) => i !== action.payload),
        total: state.total - state.items[action.payload].price
      };
    case 'CLEAR':
      return initialState;
    default:
      return state;
  }
}

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

  return (
    <div>
      <button onClick={() => dispatch({
        type: 'ADD_ITEM',
        payload: { name: 'Produto', price: 50 }
      })}>
        Adicionar Item
      </button>
      <p>Total: R$ {cart.total}</p>
    </div>
  );
}

Essa abordagem torna o código mais previsível e facilita testes, especialmente em aplicações com lógica complexa.

useContext: Compartilhamento de Estado Global

O useContext elimina a necessidade de prop drilling ao criar um canal direto de comunicação entre componentes distantes na árvore. Combinado com useReducer, cria um sistema robusto de gerenciamento de estado.

A implementação envolve criar um Context, um Provider que envolve a aplicação, e consumidores que acessam os valores. Veja um exemplo prático com tema da aplicação:

import React, { createContext, useContext, useReducer } from 'react';

const ThemeContext = createContext();

function themeReducer(state, action) {
  switch(action.type) {
    case 'TOGGLE_THEME':
      return { ...state, isDark: !state.isDark };
    case 'SET_COLOR':
      return { ...state, primaryColor: action.payload };
    default:
      return state;
  }
}

function ThemeProvider({ children }) {
  const [theme, dispatch] = useReducer(themeReducer, {
    isDark: false,
    primaryColor: '#3498db'
  });

  return (
    <ThemeContext.Provider value={{ theme, dispatch }}>
      {children}
    </ThemeContext.Provider>
  );
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme deve ser usado dentro de ThemeProvider');
  }
  return context;
}

function Header() {
  const { theme, dispatch } = useTheme();
  return (
    <header style={{ 
      background: theme.isDark ? '#000' : '#fff',
      color: theme.primaryColor 
    }}>
      <button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}>
        {theme.isDark ? '☀️' : '🌙'}
      </button>
    </header>
  );
}

Este padrão é especialmente útil para contextos que precisam ser acessados em múltiplos níveis sem passar props explicitamente.

useImperativeHandle: Expondo Métodos de Componentes

O useImperativeHandle permite que componentes filhos exponham métodos imperativos que podem ser chamados pelo pai através de refs. Use com moderação, pois quebra o padrão declarativo do React, mas é essencial para integrações com bibliotecas externas.

Considere um formulário onde o pai precisa resetar os dados programaticamente:

import React, { useRef, useImperativeHandle, forwardRef, useState } from 'react';

const Form = forwardRef(function Form(props, ref) {
  const [formData, setFormData] = useState({ name: '', email: '' });

  useImperativeHandle(ref, () => ({
    reset: () => setFormData({ name: '', email: '' }),
    getData: () => formData,
    setData: (data) => setFormData(data)
  }), [formData]);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  return (
    <form>
      <input 
        name="name" 
        value={formData.name} 
        onChange={handleChange} 
        placeholder="Nome"
      />
      <input 
        name="email" 
        value={formData.email} 
        onChange={handleChange} 
        placeholder="Email"
      />
    </form>
  );
});

function App() {
  const formRef = useRef();

  return (
    <div>
      <Form ref={formRef} />
      <button onClick={() => formRef.current.reset()}>
        Limpar Formulário
      </button>
    </div>
  );
}

Utilize este hook quando precisar controlar imperativamentebehaviors específicos como validação, envio de dados ou interação com APIs externas.

Combinando os Três Hooks em Prática

Integrar os três hooks em uma única solução oferece máxima flexibilidade. Um sistema de notificações globais exemplifica perfeitamente:

const NotificationContext = createContext();

function notificationReducer(state, action) {
  switch(action.type) {
    case 'ADD':
      return [...state, { id: Date.now(), ...action.payload }];
    case 'REMOVE':
      return state.filter(n => n.id !== action.payload);
    default:
      return state;
  }
}

function NotificationProvider({ children }) {
  const [notifications, dispatch] = useReducer(notificationReducer, []);
  const notificationRef = useRef();

  useImperativeHandle(notificationRef, () => ({
    add: (message, type = 'info') => 
      dispatch({ type: 'ADD', payload: { message, type } })
  }), []);

  return (
    <NotificationContext.Provider value={{ notifications, dispatch, notificationRef }}>
      {children}
    </NotificationContext.Provider>
  );
}

Esta combinação criar sistemas escaláveis que mantêm código limpo e testável.

Conclusão

Dominando useReducer, useContext e useImperativeHandle, você terá ferramentas suficientes para gerenciar estado complexo, compartilhá-lo globalmente e integrar comportamentos imperativos quando necessário. O segredo é escolher a ferramenta certa para cada situação: use useReducer para lógica complexa, useContext para compartilhamento entre componentes distantes, e useImperativeHandle apenas quando o padrão declarativo não for suficiente. Pratique combinando-os em pequenos projetos para internalizar esses conceitos.

Referências


Artigos relacionados