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
- Pakai Redux untuk app kecil — overhead 1000% berlebihan. Zustand atau Context cukup.
- Simpan API data di Redux/Zustand — re-invent caching layer manual. Pakai TanStack Query.
- Single global store untuk SEMUA state — rerender chaos, perf drop. Split per concern.
- Pakai Context untuk state yang sering berubah — semua consumer re-render. Pakai Zustand atau split context.
- Naik ke library terlalu cepat — start dengan useState + lift up. Pindah ke Zustand pas drilling > 3 level atau state benar-benar global.
- Gak split server/client state — campur API data + UI state di store sama = sulit invalidate, sulit cache.
- Skip TanStack Query — manual fetch di useEffect = race condition + cache nightmare. TanStack Query handle gratis.
🎯 Pilih state management — skala app
- Toy project / form simple →
useStateaja- 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.