Tips & Tricks (Diperbarui: 2/6/2026)

State Management React dengan Claude Code: Context sampai TanStack Query

Panduan merapikan state React dengan Claude Code: Context, Zustand, Jotai, TanStack Query, test, dan prompt aman.

State Management React dengan Claude Code: Context sampai TanStack Query

State management di React sering kacau bukan karena tidak memakai library, tetapi karena semua data berubah diperlakukan sama. Nilai input, modal terbuka, cart, theme preference, dan product list dari API sama-sama state, tetapi owner dan masa hidupnya berbeda.

Claude Code bisa membantu merapikan kode ini, asalkan instruksinya jelas. Jika hanya menulis “rapikan state management”, hasilnya bisa menjadi global store yang terlalu besar, cache API ganda, atau migrasi library tanpa test. Cara aman adalah meminta inventory state lebih dulu, memisahkan client state dan server state, lalu refactor satu bagian kecil.

Artikel ini membahas kapan React cukup, kapan Zustand, Jotai, dan TanStack Query membantu, serta contoh todo, cart, settings, dan products. Ada kode React/TypeScript yang bisa disalin, test, prompt aman, link resmi, dan CTA monetisasi.

Klasifikasikan state sebelum memilih library

Pertanyaan pertama: data ini milik siapa? Apakah milik UI lokal, browser client, preference yang dipersist, atau server?

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"]

Local UI state sebaiknya dekat dengan komponen. useState cocok untuk nilai sederhana, sedangkan useReducer cocok untuk screen dengan banyak aksi. Shared client state dibuat di browser dan dipakai komponen yang berjauhan, misalnya cart. Persisted preference seperti theme dan density boleh bertahan setelah reload. Server state berasal dari API dan bisa berubah di luar aplikasi.

Kapan React sudah cukup

Dokumentasi resmi Managing State dari React menyarankan struktur state yang baik, lifting state saat perlu, lalu reducer dan context untuk skala lebih besar. Ini juga urutan yang bagus untuk Claude Code.

KebutuhanPilihan awalPertimbangkan library saat
Input, tab, modaluseStateHampir tidak pernah
Screen dengan banyak aksiuseReducerReducer dipakai lintas route
Prop drilling dalamContextUpdate sering membuat re-render luas
Cart atau draft editorZustandBanyak komponen jauh mengubahnya
Theme dan densityJotaiBanyak preference kecil digabungkan
Products, orders, profile APITanStack QueryButuh cache, retry, refetch, invalidation

Aturan praktis: jika nilai dipakai kurang dari tiga tempat, bukan dari API, dan tidak perlu bertahan setelah reload, mulai dengan React built-ins.

Use case 1: Todo dengan useReducer dan Context

Todo punya aksi jelas: add, toggle, delete. Jika belum sinkron ke server, reducer dan context sudah cukup serta mudah dites.

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

Struktur ini enak untuk Claude Code. Anda bisa meminta action edited, validasi title kosong, atau test reducer tanpa mengubah semua komponen.

Use case 2: Cart dengan Zustand

Cart muncul di product page, header, drawer, dan checkout. Zustand cocok untuk store kecil berbasis hook. Middleware persist menyimpan hanya bagian yang aman.

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

Cart lokal bukan sumber kebenaran untuk checkout. Harga, stok, diskon, pajak, dan pembayaran harus divalidasi server. Tulis batas ini di prompt agar Claude Code tidak menyimpan token atau data sensitif.

Use case 3: Settings dengan Jotai

Theme, density, sidebar, dan preview mode adalah state kecil. Jotai nyaman untuk atom kecil dan nilai turunan.

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

Jangan jadikan semua hal atom. Jotai cocok untuk preference dan derived value; server cache tetap lebih cocok di TanStack Query.

Use case 4: Products dengan TanStack Query

Products, orders, dan profile adalah server state. Data bisa stale, request bisa gagal, dan perubahan bisa datang dari perangkat lain. TanStack Query v5 menangani fetching, caching, syncing, updating, dan invalidation.

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"] });
    },
  });
}

Batasnya jelas: Zustand menyimpan intent cart sementara; TanStack Query membaca products, cart server, orders, dan checkout status.

Prompt aman dan test

How Claude Code works menjelaskan siklus context, action, verification. Gunakan pola itu: minta audit dulu, bukan edit langsung.

Inspect state management aplikasi React ini. Jangan edit file dulu.

Tujuan:
- pisahkan client state dan server state
- tandai apa yang bisa tetap di useState/useReducer/Context
- jelaskan kapan Zustand, Jotai, atau TanStack Query diperlukan

Output:
- tabel inventory state
- state duplikat atau turunan
- data API yang disimpan sebagai client state
- urutan migrasi aman dengan test

Test logika murni terlebih dahulu.

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

Untuk TanStack Query, ikuti testing guide: buat QueryClient baru per test agar cache tidak bocor.

Pitfall umum: semua state masuk global store, server state disimpan di Zustand, derived value seperti total disimpan ganda, data sensitif dipersist, dan diff Claude Code diterima tanpa test, build, reload check, serta simulasi error API.

Referensi resmi: React Managing State, Zustand persist, Jotai, TanStack Query overview, Claude Code best practices.

Untuk prompt yang lebih baik, lanjutkan ke better prompts guide dan Claude Code productivity tips. Untuk template reusable, lihat produk. Untuk penerapan di repository tim, gunakan training dan konsultasi.

Setelah dicoba, hasil terbaik datang dari memindahkan server state lebih dulu ke TanStack Query. Zustand menjadi kecil, Jotai fokus pada preference, dan diff Claude Code lebih mudah direview karena inventory state sudah jelas sebelum implementasi.

#Claude Code #React #state management #Zustand #frontend
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.