Tips & Tricks (Atualizado: 02/06/2026)

Gerenciamento de estado React com Claude Code: guia prático

Organize estado React com Claude Code: Context, Zustand, Jotai, TanStack Query, testes e prompts seguros.

Gerenciamento de estado React com Claude Code: guia prático

Gerenciamento de estado em React fica confuso quando todos os dados mutáveis são tratados como a mesma coisa. Um input, um modal aberto, um carrinho, uma preferência de tema e uma lista de produtos vinda da API são state, mas têm donos e ciclos de vida diferentes.

Claude Code pode ajudar a refatorar, desde que a tarefa seja específica. “Melhore o state management” é amplo demais. Isso pode virar uma store global gigante, cache duplicado ou migração de biblioteca sem testes. O fluxo mais seguro é inventariar o estado, separar client state de server state e migrar uma fatia por vez.

Este guia mostra quando React basta, quando Zustand, Jotai e TanStack Query ajudam, e traz exemplos copiáveis para todo, carrinho, settings e produtos. Também inclui testes, prompts seguros, links oficiais e CTA de monetização.

Classifique antes de escolher a biblioteca

Primeiro pergunte quem é dono do dado. Ele pertence à UI local, ao navegador, a uma preferência persistida ou ao servidor?

flowchart TD
  A["React state management"] --> B["Local UI state"]
  A --> C["Shared client state"]
  A --> D["Persisted preferences"]
  A --> E["Server state"]
  B --> B1["input, modal, tab"]
  C --> C1["cart, wizard, editor"]
  D --> D1["theme, density, locale"]
  E --> E1["products, orders, profile from API"]

Estado local de UI fica perto do componente. useState resolve valores simples, e useReducer ajuda quando há várias ações. Estado de cliente compartilhado nasce no navegador e é usado por componentes distantes, como carrinho. Preferências persistidas sobrevivem ao reload. Server state vem de API e pode mudar fora do app.

Quando React é suficiente

A documentação de Managing State do React recomenda estruturar bem o estado, elevar quando necessário e escalar com reducer e context. Use essa ordem antes de pedir uma nova biblioteca ao Claude Code.

NecessidadePrimeira opçãoConsidere biblioteca quando
Input, tab, modaluseStateQuase nunca
Tela com várias açõesuseReducerO reducer aparece em várias rotas
Prop drilling profundoContextAtualizações frequentes re-renderizam demais
Carrinho ou draftZustandComponentes distantes atualizam o mesmo valor
Tema e densidadeJotaiMuitas preferências pequenas se combinam
Produtos, pedidos, perfil APITanStack QueryCache, retry, refetch e invalidation são necessários

Regra simples: se o valor aparece em menos de três lugares, não vem de API e não precisa sobreviver ao reload, comece com React.

Caso 1: Todo com useReducer e Context

Uma lista de tarefas tem ações claras: adicionar, alternar, remover. Sem sincronização com servidor, reducer e context são um começo seguro.

import {
  createContext,
  useContext,
  useMemo,
  useReducer,
  type Dispatch,
  type ReactNode,
} from "react";

export type Todo = {
  id: string;
  title: string;
  done: boolean;
};

type TodoAction =
  | { type: "added"; title: string }
  | { type: "toggled"; id: string }
  | { type: "deleted"; id: string };

export function todoReducer(todos: Todo[], action: TodoAction): Todo[] {
  switch (action.type) {
    case "added":
      return [
        ...todos,
        { id: crypto.randomUUID(), title: action.title.trim(), done: false },
      ];
    case "toggled":
      return todos.map((todo) =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo,
      );
    case "deleted":
      return todos.filter((todo) => todo.id !== action.id);
    default:
      return todos;
  }
}

const TodoStateContext = createContext<Todo[] | null>(null);
const TodoDispatchContext = createContext<Dispatch<TodoAction> | null>(null);

export function TodoProvider({ children }: { children: ReactNode }) {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const stateValue = useMemo(() => todos, [todos]);

  return (
    <TodoStateContext.Provider value={stateValue}>
      <TodoDispatchContext.Provider value={dispatch}>
        {children}
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}

export function useTodos() {
  const value = useContext(TodoStateContext);
  if (value === null) throw new Error("useTodos must be used in TodoProvider");
  return value;
}

export function useTodoDispatch() {
  const value = useContext(TodoDispatchContext);
  if (value === null) {
    throw new Error("useTodoDispatch must be used in TodoProvider");
  }
  return value;
}

Essa estrutura ajuda o Claude Code a fazer mudanças pequenas: adicionar ação edited, bloquear título vazio ou criar testes do reducer.

Caso 2: Carrinho com Zustand

Carrinho aparece em página de produto, header, drawer e checkout. Zustand cria uma store leve como hook. O middleware persist deve guardar só o que é seguro.

import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";

export type CartItem = {
  productId: string;
  name: string;
  price: number;
  quantity: number;
};

type CartState = {
  items: CartItem[];
  currency: "JPY" | "USD";
  addItem: (item: Omit<CartItem, "quantity">) => void;
  removeItem: (productId: string) => void;
  setQuantity: (productId: string, quantity: number) => void;
  clear: () => void;
  total: () => number;
};

export const useCartStore = create<CartState>()(
  persist(
    (set, get) => ({
      items: [],
      currency: "USD",
      addItem: (item) =>
        set((state) => {
          const current = state.items.find(
            (entry) => entry.productId === item.productId,
          );
          if (current) {
            return {
              items: state.items.map((entry) =>
                entry.productId === item.productId
                  ? { ...entry, quantity: entry.quantity + 1 }
                  : entry,
              ),
            };
          }
          return { items: [...state.items, { ...item, quantity: 1 }] };
        }),
      removeItem: (productId) =>
        set((state) => ({
          items: state.items.filter((item) => item.productId !== productId),
        })),
      setQuantity: (productId, quantity) =>
        set((state) => ({
          items: state.items.map((item) =>
            item.productId === productId
              ? { ...item, quantity: Math.max(1, quantity) }
              : item,
          ),
        })),
      clear: () => set({ items: [] }),
      total: () =>
        get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    }),
    {
      name: "cart-v1",
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        items: state.items,
        currency: state.currency,
      }),
    },
  ),
);

O carrinho local não valida preço final. Servidor precisa confirmar preço, estoque, desconto, impostos e pagamento. No prompt, diga ao Claude Code quais campos podem persistir e quais dependem da API.

Caso 3: Settings com Jotai

Tema, densidade, sidebar e preview são valores pequenos. Jotai funciona bem quando você quer atoms pequenos e valores derivados.

import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";

export const themeAtom = atomWithStorage<"light" | "dark">(
  "settings.theme",
  "light",
);

export const densityAtom = atomWithStorage<"comfortable" | "compact">(
  "settings.density",
  "comfortable",
);

export const sidebarOpenAtom = atom(true);

export const settingsLabelAtom = atom((get) => {
  const theme = get(themeAtom) === "dark" ? "dark theme" : "light theme";
  const density =
    get(densityAtom) === "compact" ? "compact layout" : "comfortable layout";
  return `${theme}, ${density}`;
});

export const resetSettingsAtom = atom(null, (_get, set) => {
  set(themeAtom, "light");
  set(densityAtom, "comfortable");
  set(sidebarOpenAtom, true);
});

O cuidado é não atomizar tudo. Preferências e derivados combinam com Jotai; cache de API e regras de mutation combinam com TanStack Query.

Caso 4: Produtos e pedidos com TanStack Query

Produtos, pedidos e perfil são server state. Podem ficar stale, falhar ou mudar em outro dispositivo. TanStack Query v5 cuida de fetching, caching, syncing e updating.

import {
  QueryClient,
  QueryClientProvider,
  useMutation,
  useQuery,
  useQueryClient,
} from "@tanstack/react-query";
import type { ReactNode } from "react";

type Product = {
  id: string;
  name: string;
  price: number;
  stock: number;
};

const queryClient = new QueryClient();

export function ProductsProvider({ children }: { children: ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
}

async function fetchProducts(category: string): Promise<Product[]> {
  const response = await fetch(`/api/products?category=${category}`);
  if (!response.ok) throw new Error("Failed to fetch products");
  return response.json() as Promise<Product[]>;
}

async function addCartLine(productId: string) {
  const response = await fetch("/api/cart/lines", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ productId }),
  });
  if (!response.ok) throw new Error("Failed to add cart line");
  return response.json();
}

export function useProducts(category: string) {
  return useQuery({
    queryKey: ["products", category],
    queryFn: () => fetchProducts(category),
    staleTime: 60_000,
  });
}

export function useAddCartLine() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addCartLine,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["cart"] });
    },
  });
}

A fronteira prática: Zustand guarda intenção temporária no navegador; TanStack Query busca produto, pedido, carrinho confirmado e checkout no servidor.

Prompts seguros e testes

How Claude Code works descreve o ciclo de contexto, ação e verificação. Use isso no prompt: primeiro peça inspeção, não edição.

Inspecione o gerenciamento de estado desta app React. Ainda não edite arquivos.

Objetivos:
- separar client state e server state
- identificar o que pode ficar em useState/useReducer/Context
- justificar Zustand, Jotai ou TanStack Query quando fizer sentido

Retorne:
- inventário de estados
- state duplicado ou derivado
- dados de API salvos como client state
- ordem segura de migração com testes

Teste lógica pura primeiro.

import { describe, expect, it, vi } from "vitest";
import { todoReducer, type Todo } from "./TodoProvider";

describe("todoReducer", () => {
  it("adds a todo with a generated id", () => {
    vi.spyOn(crypto, "randomUUID").mockReturnValue("todo-1");

    const result = todoReducer([], { type: "added", title: "Write tests" });

    expect(result).toEqual([
      { id: "todo-1", title: "Write tests", done: false },
    ]);
  });

  it("toggles a todo", () => {
    const initial: Todo[] = [{ id: "todo-1", title: "Ship", done: false }];
    const result = todoReducer(initial, { type: "toggled", id: "todo-1" });
    expect(result[0].done).toBe(true);
  });
});

Para TanStack Query, siga o testing guide: crie um QueryClient novo por teste.

Armadilhas comuns: colocar tudo em store global, guardar server state no Zustand, duplicar valores derivados, persistir dados sensíveis e aceitar diff do Claude Code sem test, build e revisão manual com reload e erro de API.

Referências oficiais: React Managing State, Zustand persist, Jotai, TanStack Query overview, Claude Code best practices.

Para melhorar prompts, leia o guia de prompts e as dicas de produtividade Claude Code. Para templates, veja produtos. Para aplicar em um repositório real de equipe, use training e consultoria.

Na prática, o maior ganho veio ao mover server state primeiro para TanStack Query. Zustand ficou menor, Jotai ficou focado em preferências e os diffs do Claude Code ficaram mais fáceis de revisar porque havia inventário e plano antes da implementação.

#Claude Code #React #gerenciamento de estado #Zustand #frontend
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.