useReducer — React

Pas state cuma 1-2 nilai sederhana, useState paling clean. Tapi bayangin shopping cart: tambah item, hapus item, update quantity, terapkan kupon, reset…

Kalau sebuah state bisa diubah dengan banyak cara (tambah, kurang, ganti langkah, reset), aturan perubahannya gampang tercecer di banyak handler. useReducer mengumpulkan semua aturan itu di satu function bernama reducer. Bayangin warung makan. Kamu gak masuk dapur dan mengubah pesanan sendiri. Kamu menulis slip, misalnya "tambah satu", lalu menyerahkannya. Koki membaca slip itu, mengecek pesanan yang sekarang, dan menulis pesanan baru menurut buku aturannya. Slip itu action, menyerahkannya itu dispatch, buku aturan koki itu reducer, dan pesanan yang sekarang itu state.

🧪 Tebakan cepat: kamu menjalankan dispatch({ type: "incremnt" }), salah ketik satu huruf. Yang terjadi: muncul error, count jadi 0, atau gak ada yang berubah? Tebak dulu, jawabannya ada di bagian "Reducer: buku aturannya".

Tiga bagian: state, action, reducer

Reducer: buku aturannya

const initialState = { count: 0, step: 1 }

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + state.step }
    case "decrement":
      return { ...state, count: state.count - state.step }
    case "setStep":
      return { ...state, step: action.step }
    case "reset":
      return initialState
    default:
      return state
  }
}

Reducer harus pure: cuma menghitung state baru dari state lama dan action. Gak boleh fetch, pasang timer, atau mengubah object state yang lama.

Pakai di komponen: useReducer dan dispatch

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <input
        type="range"
        min="1"
        max="5"
        value={state.step}
        onChange={(e) => dispatch({ type: "setStep", step: Number(e.target.value) })}
      />
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  )
}

Ini yang tercatat di console Lab kalau kamu menekan tombol-tombolnya berurutan:

Action yang dikirim State sebelum State sesudah Tampil
{ type: "increment" } { count: 0, step: 1 } { count: 1, step: 1 } 1
{ type: "increment" } { count: 1, step: 1 } { count: 2, step: 1 } 2
{ type: "setStep", step: 5 } { count: 2, step: 1 } { count: 2, step: 5 } 2
{ type: "increment" } { count: 2, step: 5 } { count: 7, step: 5 } 7
{ type: "incremnt" } { count: 7, step: 5 } { count: 7, step: 5 }, sama 7
{ type: "reset" } { count: 7, step: 5 } { count: 0, step: 1 } 0

Kapan pakai useReducer, bukan useState

⚠️ Jebakan yang sering ketemu

TL;DR: useReducer(reducer, initialState) menerima dua argumen, function reducer dan state awal, lalu mengembalikan [state, dispatch]. Komponen mengirim action lewat dispatch({ type: "increment" }), dan action boleh membawa data ({ type: "setStep", step: 5 }). Reducer adalah function pure (state, action) => stateBaru dengan switch (action.type): tiap case mengembalikan object baru ({ ...state, count: ... }), dan default: return state membuat action salah ketik gak mengubah apa-apa. Pakai useReducer untuk state kompleks dengan banyak aksi. Untuk nilai sederhana, useState cukup.

Yang akan kamu pelajari