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?…

State management artinya memutuskan di mana tiap state disimpan, dan siapa yang boleh membaca dan mengubahnya. Patokannya sederhana: simpan state sedekat mungkin dengan komponen yang memakainya, dan naikkan cuma kalau perlu. Bayangin kantor. Catatan pribadi cukup di sticky note di mejamu. Angka yang dipakai dua orang satu tim ditulis di papan tulis tim, dan ketua tim yang memegang spidolnya. Pengumuman untuk seluruh gedung disiarkan lewat speaker, dan semua orang di gedung ikut mendengar. Kalau pengumumannya banyak dan sering, kantor memasang papan langganan: tiap orang cuma diberi tahu topik yang dia ikuti.

🧪 Tebakan cepat: komponen Counter dan Total bersebelahan dan butuh angka yang sama. Perlu library state management? Tebak dulu, jawabannya ada di bagian "Tangga: mulai dari yang paling dekat".

Tangga: mulai dari yang paling dekat

Tempat state Dipakai oleh Contoh Alat
Lokal 1 komponen isi kolom pencarian, menu terbuka atau tertutup useState di komponen itu
Lifting state up 2 sampai 3 komponen berdekatan angka yang dipakai Counter dan Total state di parent terdekat, dikirim lewat props
Context banyak komponen di banyak level, jarang berubah tema, user yang login, bahasa createContext dan useContext, sering bareng useReducer
Store state global yang kompleks, sering berubah, dibaca banyak komponen keranjang belanja library seperti Zustand atau Redux Toolkit

Naik satu anak tangga cuma kalau anak tangga di bawahnya mulai merepotkan. Counter dan Total cukup memakai lifting state up: simpan angkanya di parent mereka, lalu kirim ke keduanya lewat props. Gak perlu library. Itu jawaban tebakannya.

Library store dipakai kalau ada state global yang kompleks dan dipakai banyak komponen di berbagai level, sampai Context terasa berat. Data dari server (hasil fetch ke API) beda lagi: itu biasanya diurus library fetching seperti TanStack Query, bukan disalin ke store.

useReducer + Context: satu sumber untuk banyak komponen

const ThemeContext = createContext()

function themeReducer(state, action) {
  if (action.type === "toggle") {
    return { theme: state.theme === "dark" ? "light" : "dark" }
  }
  return state
}

function ThemeDisplay() {
  const { state } = useContext(ThemeContext)
  return <p id="theme">{state.theme}</p>
}

function App() {
  const [state, dispatch] = useReducer(themeReducer, { theme: "dark" })
  return (
    <ThemeContext.Provider value={{ state, dispatch }}>
      <ThemeDisplay />
    </ThemeContext.Provider>
  )
}

Untuk daftar, polanya sama. Reducer dengan aksi add mengembalikan array baru [...state, action.payload], dan TodoList membuat satu <li> per isi state dengan map:

function TodoList() {
  const { state } = useContext(TodoContext)
  return (
    <ul id="todos">
      {state.map((todo, i) => <li key={i}>{todo}</li>)}
    </ul>
  )
}

AddTodo mengirim dispatch({ type: "add", payload: text }), lalu setText("") supaya kolom isiannya kosong lagi.

Siapa yang ikut render ulang

Tempat state menentukan komponen mana yang render ulang waktu state-nya berubah. Contohnya toko kecil: App berisi Header (menampilkan jumlah isi keranjang), Produk (tombol Tambah), Sidebar, dan Footer. Klik Tambah sekali:

Keranjang disimpan di Yang render ulang tiap klik Jumlah
State di App (lifting) App, Header, Produk, Sidebar, Footer 5
Context (CartProvider dengan useReducer) CartProvider, Header, Produk 3
Store dengan selector Header 1

Store paling ringkas untuk React adalah Zustand:

import { create } from "zustand"

const useCart = create((set) => ({
  jumlah: 0,
  tambah: () => set((s) => ({ jumlah: s.jumlah + 1 })),
}))

function Header() {
  const jumlah = useCart((s) => s.jumlah) // render ulang cuma kalau jumlah berubah
  return <span>Keranjang: {jumlah}</span>
}

function Produk() {
  const tambah = useCart((s) => s.tambah) // function yang sama terus
  return <button onClick={tambah}>Tambah</button>
}

Library populer lainnya Redux (sekarang ditulis dengan Redux Toolkit), yang banyak dipakai di project besar dengan tim besar. Ada juga Jotai dan MobX.

⚠️ Jebakan yang sering ketemu

TL;DR: Simpan state sedekat mungkin dengan pemakainya: useState lokal untuk satu komponen, lifting state up ke parent terdekat untuk 2 sampai 3 komponen, Context (sering bareng useReducer) untuk data yang dibaca banyak komponen tapi jarang berubah, dan store seperti Zustand atau Redux untuk state global yang kompleks dan sering berubah. createContext, useReducer, lalu <Context.Provider value={{ state, dispatch }}> membagikan state tanpa props, dan useContext membacanya. Context membuat semua consumer render ulang, store dengan selector cuma yang membaca bagian yang berubah. Jangan buru-buru memakai library.

Yang akan kamu pelajari