State Management Patterns — React

Pas app berkembang, state spread di mana-mana. Cart state di komponen , user data di , theme di , filter list di . Bagaimana komponen-komponen ini sync data?…

Pas app berkembang, state spread di mana-mana. Cart state di komponen <Cart>, user data di <Header>, theme di <App>, filter list di <Sidebar>. Bagaimana komponen-komponen ini sync data? Pas sederhana: lift state up + props. Pas kompleks: pattern state management. Tujuan: SATU sumber kebenaran untuk tiap data, akses dari mana saja, update predictable. Pattern bervariasi dari built-in (useReducer + Context) sampai library spesialis (Zustand, Redux, Jotai). Pilih sesuai kebutuhan project.

💡 2026 favorite stack: TanStack Query + Zustand. Kebanyakan project React modern split state ke 2 kategori: server state (data dari API → TanStack Query handle cache+refetch) + client state (UI state, theme, modal → Zustand kalau global, useState kalau lokal). Redux mulai jarang di project baru — overhead boilerplate-nya gede dibanding alternative.

useReducer + Context — Built-in Pattern

const AppContext = createContext()

function appReducer(state, action) {
  switch (action.type) {
    case "SET_USER": return { ...state, user: action.payload }
    case "TOGGLE_THEME":
      return { ...state, theme: state.theme === "dark" ? "light" : "dark" }
    default: return state
  }
}

function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, {
    user: null,
    theme: "dark",
  })

  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  )
}

function Header() {
  const { state, dispatch } = useContext(AppContext)
  return (
    <button onClick={() => dispatch({ type: "TOGGLE_THEME" })}>
      Theme: {state.theme}
    </button>
  )
}

Cocok untuk app kecil-medium dengan state global terbatas.

Zustand — Library Modern, Minimalis

import { create } from "zustand"

const useStore = create((set) => ({
  count: 0,
  user: null,
  theme: "dark",
  increment: () => set(state => ({ count: state.count + 1 })),
  setUser: (user) => set({ user }),
  toggleTheme: () => set(state => ({
    theme: state.theme === "dark" ? "light" : "dark"
  })),
}))

// Pemakaian — gak perlu Provider, langsung pakai
function Counter() {
  const { count, increment } = useStore()
  return <button onClick={increment}>Count: {count}</button>
}

function Header() {
  const theme = useStore(state => state.theme)  // selector — re-render cuma kalau theme berubah
  return <header className={theme}>...</header>
}

Zustand: gak perlu Provider, gak perlu reducer pattern, performa otomatis (selector-based).

Redux Toolkit (RTK)

import { createSlice, configureStore } from "@reduxjs/toolkit"

const counterSlice = createSlice({
  name: "counter",
  initialState: { value: 0 },
  reducers: {
    incremented: state => { state.value += 1 },  // Immer auto-handle immutability
    decremented: state => { state.value -= 1 },
  },
})

export const { incremented, decremented } = counterSlice.actions
export const store = configureStore({ reducer: { counter: counterSlice.reducer } })

// Pemakaian
import { useSelector, useDispatch } from "react-redux"
function Counter() {
  const value = useSelector(state => state.counter.value)
  const dispatch = useDispatch()
  return <button onClick={() => dispatch(incremented())}>{value}</button>
}

RTK = Redux modern (no Redux ngeselin lagi). Cocok untuk project enterprise dengan tim besar.

Jotai — Atomic State

import { atom, useAtom } from "jotai"

const countAtom = atom(0)
const themeAtom = atom("dark")

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Jotai: state pecah jadi atom-atom kecil, fine-grained reactivity.

Server State vs Client State — Pisahin

// SERVER STATE — data dari API → TanStack Query
function UserProfile({ userId }) {
  const { data: user } = useQuery({
    queryKey: ["users", userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json())
  })
  return <h1>{user?.name}</h1>
}

// CLIENT STATE — UI state → Zustand atau useState
const useUIStore = create(set => ({
  isModalOpen: false,
  toggleModal: () => set(state => ({ isModalOpen: !state.isModalOpen })),
}))

Anti-pattern lawas: simpan API data di Redux. Modern: API data → TanStack Query (handle cache, refetch, dedup), UI state → Zustand/useState.

🎭 Analogi sehari-hari

State management pilihan = infrastruktur kantor. useState = sticky note di meja sendiri (lokal, simple). useReducer + Context = papan tulis ruang meeting (shared di tim, perlu setup ruang). Zustand = Telegram channel kantor (broadcast cepat, semua join, gak perlu invite formal — modern + minimalis). Redux Toolkit = ERP system enterprise (powerful, terintegrasi, formal — cocok perusahaan besar tapi butuh training). Jotai = WhatsApp groups per topik (atom-atom kecil terpisah, gabung yang relevan). TanStack Query = sistem inventory yang track stok dari gudang real-time (server state, auto-sync). Pilih sesuai ukuran tim + kompleksitas project.

⚠️ Jebakan yang sering ditemui

🎯 Pilih state management — skala app

  • Toy project / form simpleuseState aja
  • Small app (1-3 page, simple state)useState + lifting up
  • Medium app (5-10 page, sharing state)useState + Context + custom hooks
  • Medium app dengan complex client state → Zustand (lightweight) atau Jotai
  • Enterprise dengan tim besar + complex flows → Redux Toolkit
  • App dengan banyak API data → TanStack Query (always) + Zustand (UI state)
  • State yang shareable URL (filters, page) → URL params (router)

Aturan: state TIDAK dipindah ke library sampai useState mulai painful. Server state TINGGAL pindah ke TanStack Query — gak ada alasan handle manual.

TL;DR: Pisahin state ke 2 kategori: server state (API data → TanStack Query) + client state (UI state → useState/Zustand). Built-in: useState (lokal), Context + useReducer (global kecil). Library: Zustand (minimalis modern), Redux Toolkit (enterprise), Jotai (atomic). Pilih sesuai ukuran app — jangan over-engineer. Modern stack 2026: TanStack Query + Zustand. Mulai sederhana, naik ke library saat kompleksitas justify.

Yang akan kamu pelajari