ReactJS #22: useReducer – Alternatif useState untuk State yang Rumit


ReactJS #22: useReducer

Alternatif useState untuk State yang Rumit

Halo, koki React! 👨‍🍳

Selama ini kita menggunakan useState untuk mengelola state. Tapi bagaimana kalau state kita rumit, misalnya keranjang belanja dengan banyak item, atau form multi-step? useState bisa membuat kode kita berantakan karena banyak fungsi setter dan logika tersebar. Nah, React punya alternatif yang lebih teratur: useReducer!

💡 Analogi: Bayangkan kamu ingin membuat masakan kompleks (misal rendang). Kalau pakai useState, seperti kamu punya banyak juru masak yang masing-masing mengubah bahan secara acak. Dengan useReducer, kamu punya satu koki utama (reducer) yang menerima pesanan (action) dan mengubah bahan (state) sesuai resep. Semua perubahan jadi terpusat dan mudah dilacak.

🧐 Kapan Perlu useReducer?

useReducer cocok untuk situasi:

  • State punya banyak sub-nilai atau struktur kompleks (objek dalam objek, array).
  • Perubahan state bergantung pada state sebelumnya secara rumit.
  • Logika update state tersebar di banyak tempat (banyak event handler).
  • Kamu ingin kode lebih mudah diuji dan diprediksi.

Contoh klasik: keranjang belanja (tambah item, hapus item, ubah jumlah, dll).

📜 Konsep useReducer

useReducer menerima dua argumen: reducer function dan initial state. Ia mengembalikan array berisi state saat ini dan fungsi dispatch.

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: fungsi yang menentukan bagaimana state berubah berdasarkan action. Mirip koki yang menerima pesanan dan mengubah bahan.
  • initialState: nilai awal state.
  • dispatch: fungsi untuk mengirim action ke reducer. Ibaratnya, kamu memanggil pelayan dan berkata "Tambah item ini ke keranjang".

Setiap action biasanya berupa objek dengan properti type (string) dan payload (data tambahan).

🧪 Contoh: Keranjang Belanja dengan useReducer

Mari kita buat keranjang belanja sederhana dengan useReducer.

1. Tentukan Initial State

const initialState = { items: [], total: 0 };

2. Buat Reducer Function

function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': { // Cek apakah item sudah ada di keranjang const existingItem = state.items.find(item => item.id === action.payload.id); if (existingItem) { // Jika ada, tambah jumlahnya const updatedItems = state.items.map(item => item.id === action.payload.id ? { ...item, quantity: item.quantity + 1 } : item ); return { ...state, items: updatedItems, total: state.total + action.payload.price }; } else { // Jika belum ada, tambah item baru dengan quantity 1 const newItem = { ...action.payload, quantity: 1 }; return { ...state, items: [...state.items, newItem], total: state.total + action.payload.price }; } } case 'REMOVE_ITEM': { const itemToRemove = state.items.find(item => item.id === action.payload.id); if (!itemToRemove) return state; if (itemToRemove.quantity > 1) { // Kurangi jumlah const updatedItems = state.items.map(item => item.id === action.payload.id ? { ...item, quantity: item.quantity - 1 } : item ); return { ...state, items: updatedItems, total: state.total - itemToRemove.price }; } else { // Hapus item const updatedItems = state.items.filter(item => item.id !== action.payload.id); return { ...state, items: updatedItems, total: state.total - itemToRemove.price }; } } case 'CLEAR_CART': return initialState; default: return state; } }

3. Gunakan useReducer di Komponen

import { useReducer } from 'react'; function ShoppingCart() { const [cart, dispatch] = useReducer(cartReducer, initialState); const products = [ { id: 1, name: 'Buku React', price: 75000 }, { id: 2, name: 'Mainan Robot', price: 120000 }, { id: 3, name: 'Pensil Warna', price: 25000 } ]; const addToCart = (product) => { dispatch({ type: 'ADD_ITEM', payload: product }); }; const removeFromCart = (product) => { dispatch({ type: 'REMOVE_ITEM', payload: product }); }; const clearCart = () => { dispatch({ type: 'CLEAR_CART' }); }; return ( <div> <h3>🛍️ Daftar Produk</h3> {products.map(product => ( <div key={product.id} style={{ margin: '10px 0' }}> {product.name} - Rp {product.price.toLocaleString()} <button onClick={() => addToCart(product)}>➕ Tambah</button> </div> ))} <h3>🛒 Keranjang Belanja</h3> {cart.items.length === 0 ? ( <p>Keranjang kosong</p> ) : ( <> {cart.items.map(item => ( <div key={item.id} className="cart-item"> <span>{item.name} (x{item.quantity})</span> <span>Rp {(item.price * item.quantity).toLocaleString()}</span> <button onClick={() => removeFromCart(item)}>➖ Kurangi</button> </div> ))} <p><strong>Total: Rp {cart.total.toLocaleString()}</strong></p> <button onClick={clearCart}>🗑️ Kosongkan Keranjang</button> </> )} </div> ); }

Perhatikan: semua logika perubahan state ada di reducer, komponen hanya mengirim action. Ini membuat kode lebih terstruktur dan mudah dipelihara.

🛒 Simulasi Keranjang

Produk: Buku React (Rp75.000)

Produk: Mainan Robot (Rp120.000)

Keranjang:

Buku React (x2) - Rp150.000
Mainan Robot (x1) - Rp120.000

Total: Rp270.000

📦 useReducer vs useState

useState useReducer
Sederhana, cocok untuk state mandiri (angka, string, boolean) Cocok untuk state kompleks (objek dengan banyak field, array)
Logika update tersebar di banyak event handler Semua logika terpusat di reducer
Mudah dipahami untuk pemula Butuh sedikit pemahaman tambahan, tapi lebih terstruktur

🧪 Latihan: Todo List dengan useReducer

Buat ulang aplikasi Todo List dari artikel #15 menggunakan useReducer. Fitur:

  • Tambah todo
  • Tandai selesai (toggle)
  • Hapus todo
  • Hapus semua yang selesai

Struktur state: array of todos, setiap todo punya id, text, completed.

Petunjuk: buat reducer dengan action types: ADD, TOGGLE, DELETE, CLEAR_COMPLETED.

💡 Tips: Gunakan Date.now() untuk ID. Di reducer, selalu kembalikan state baru (jangan ubah langsung).

⚠️ Hal Penting

  • Immutability: Jangan ubah state langsung. Selalu buat salinan baru (spread operator, map, filter).
  • Action type biasanya ditulis sebagai konstanta untuk menghindari typo, tapi untuk belajar, string langsung cukup.
  • Reducer harus pure function: tidak boleh melakukan side effect (fetch, setTimeout, dll). Side effect dilakukan di komponen (misal useEffect), lalu dispatch action.

🚀 Kesimpulan

  • useReducer adalah alternatif useState untuk state yang kompleks atau banyak logika update.
  • Pola reducer memisahkan logika bisnis dari komponen, membuat kode lebih terprediksi dan mudah diuji.
  • Gunakan dispatch untuk mengirim action dengan tipe tertentu.
  • useReducer sangat berguna untuk state seperti keranjang belanja, form multi-step, atau data dengan banyak interaksi.

Di artikel selanjutnya kita akan belajar tentang Custom Hooks – membuat hook sendiri untuk memakai ulang logika stateful. Sampai jumpa!

Lebih baru Lebih lama

نموذج الاتصال