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,countjadi 0, atau gak ada yang berubah? Tebak dulu, jawabannya ada di bagian "Reducer: buku aturannya".
Tiga bagian: state, action, reducer
- State: data yang sekarang, misalnya
{ count: 0, step: 1 }. - Action: object biasa yang menjelaskan apa yang terjadi. Minimal punya
type, misalnya{ type: "increment" }. Boleh membawa data tambahan:{ type: "setStep", step: 5 }. - Reducer: function
(state, action) => stateBaru. Dia menerima state sekarang dan action, lalu mengembalikan state berikutnya. dispatch(action): cara komponen mengirim action ke 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
}
}
switch (action.type)memilih aturan sesuai jenis action.- Tiap
casemengembalikan object baru....statemenyalin field yang gak berubah, lalu field yang berubah ditulis sesudahnya. setStepmemakai data yang dibawa action-nya:action.step.resetmengembalikaninitialState, jadicountdanstepdua-duanya kembali ke awal.default: return statemenangani action yang gak dikenal: state lama dikembalikan apa adanya. Jadi"incremnt"gak bikin error dan gak mengubah apa pun. Itu jawaban tebakannya. Karena diam-diam, salah ketik seperti ini gampang terlewat.
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>
)
}
useReducer(reducer, initialState)menerima dua argumen: function reducer dan state awal.- Dia mengembalikan
[state, dispatch]: state sekarang, dan function untuk mengirim action. - Komponennya gak menghitung apa-apa. Dia cuma bilang apa yang terjadi, reducer yang memutuskan hasilnya, lalu React render ulang dengan state yang baru.
- Di editor latihan,
useReducersudah tersedia tanpaimport. Di project sungguhan, tulisimport { useReducer } from "react".
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
useStateuntuk nilai yang berdiri sendiri dengan perubahan sederhana: saklar buka tutup, isian satu kolom, counter tambah satu.useReduceruntuk state kompleks dengan banyak aksi, atau beberapa nilai yang berubah bersama: keranjang belanja (tambah, hapus, ubah jumlah, kosongkan), form beberapa langkah (lanjut, kembali, ulang).- Karena semua aturannya ada di satu function biasa, reducer gampang diuji tanpa render:
reducer({ count: 2, step: 5 }, { type: "increment" })pasti{ count: 7, step: 5 }. Riwayat action-nya juga bisa dicatat, seperti console di Lab. - Pola action dan reducer ini juga dipakai library state seperti Redux. Pasangan
useReducerdengan Context dibahas lagi di materi State Management.
⚠️ Jebakan yang sering ketemu
- Mengubah state lama.
state.count++lalureturn statemengembalikan object yang sama. React membandingkan state lama dan baru denganObject.is, menganggap gak ada perubahan, dan layarnya gak diperbarui. Selalu kembalikan object baru. - Lupa
default. Tanpadefault: return state, action yang gak dikenal membuat reducer mengembalikanundefined, lalustate.countlangsung error. - Lupa
...state.return { count: state.count + 1 }di reducer yang juga punyastepmembuatstephilang dari state. - Side effect di reducer. Fetch, timer, atau
localStoragetaruh di event handler atauuseEffect. Di mode development (Strict Mode), React memanggil reducer dua kali untuk memastikan reducer-nya pure.
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.