React State Management: Context API & useReducer Lengkap
Halo gaes, apa kabar nih para pejuang React JS? Pernah ngerasain kan gimana pusingnya kalau aplikasi React udah makin gede, terus tiba-tiba props drilling jadi momok menakutkan? Kamu harus nge-pass props dari atas ke bawah, ke bawah lagi, sampai ke komponen paling dalam cuma buat sepotong data. Beuh, PR banget, ngab!
Nah, di artikel ini, kita bakal spill jurus sakti buat ngatasin itu semua: Context API dan useReducer. Kombinasi dua hooks ini tuh powerful banget buat ngelola state yang kompleks di aplikasi React kamu biar lebih rapi, scalable, dan pastinya anti ribet! Skuy, langsung aja kita bedah!
Kenalan Dulu Sama Dua Bintang Tamu Kita!
Sebelum nyemplung ke kodenya, kita pahami dulu konsep dasar Context API dan useReducer secara santuy.
Context API: Solusi Anti Props Drilling
Bayangin Context API itu kayak papan buletin digital di kantor. Kamu bisa nulis pengumuman penting di situ (state) dan siapa aja yang ada di kantor (komponen) bisa baca pengumuman itu tanpa harus dikasih tahu satu per satu.
createContext: Ini buat bikin papan buletinnya. Di sinilah kita definisikan "slot" buat data yang mau dibagi.Provider: Ini yang nempel di papan buletin. Dia yang "menyediakan" data ke semua komponen di bawahnya.useContext: Ini kayak mata-mata yang baca papan buletin. Komponen yang butuh data tinggal panggiluseContextdan boom! Data langsung nyampe tanpa harus dilempar-lempar lewat props.
Vibes-nya: Cocok banget buat data global yang diakses banyak komponen tapi jarang di-update, atau data yang memang spesifik buat sub-tree tertentu.
useReducer: Ketika useState Udah Nggak Cukup!
Pernah pake useState kan? Nah, useReducer ini bisa dibilang kakak tiri useState yang lebih jagoan buat ngurusin state yang kompleks. Kalau state kamu punya banyak transisi (bisa berubah dari A ke B, B ke C, A ke D) dan logic update-nya lumayan panjang, useReducer ini jawabannya.
reducerfunction: Ini inti dariuseReducer. Dia fungsi murni yang nerima dua argumen:statesaat ini danactionyang mau dilakuin. Tugasnya? Ngembaliinstateyang baru berdasarkanactiontadi. Ingat, pure function ya, gaes! Nggak boleh ada side effect di sini.initialState: Ya, ini nilai awal dari state kita.dispatchfunction: Ini fungsi yang kamu panggil buat "ngasih tahu" reducer kalau ada sesuatu yang harus diubah. Kamu bakal nge-dispatchsebuah objekactionyang biasanya punya propertitype(buat nandain aksi apa) danpayload(data yang dibawa aksi itu).
Vibes-nya: Cocok buat state yang kompleks, punya banyak related state (misal: isLoading, data, error), atau kalau kamu suka pola Redux tapi mau versi native React-nya.
The Dream Team: Context API + useReducer
Nah, kalau mereka berdua digabung, jadinya mantap banget! useReducer kita pake buat ngelola logic state yang kompleks (misal: nambahin item, ngapus item, toggle status). Sementara itu, Context API yang akan jadi kurir setia, ngebagi state terbaru dan fungsi dispatch ke semua komponen yang butuh, tanpa harus props drilling lagi. Ini bikin kode kamu lebih modular, gampang di-debug, dan performanya bisa lebih optimal kalau diatur dengan baik.
Hands-on: Bikin Aplikasi To-Do List Sederhana!
Skuy, langsung aja kita implementasiin di project React biar makin paham! Kita bakal bikin To-Do List sederhana.
Struktur File (Biar Rapi)
src/
├── components/
│ ├── AddTodoForm.jsx
│ ├── TodoItem.jsx
│ └── TodoList.jsx
├── context/
│ ├── TodoContext.jsx
│ └── todoReducer.js
├── App.jsx
└── main.jsx
Langkah 1: Siapin Reducer dan Initial State (src/context/todoReducer.js)
Pertama, kita definisiin reducer kita. Ini bakal jadi "otak" yang ngurusin semua perubahan state To-Do List.
// src/context/todoReducer.js
export const initialTodosState = [
{ id: 1, text: 'Belajar React Context API', completed: false },
{ id: 2, text: 'Nulis artikel ini', completed: true },
{ id: 3, text: 'Minum kopi', completed: false },
];
export const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: Date.now(), // ID unik sederhana
text: action.payload,
completed: false,
},
];
case 'TOGGLE_TODO':
return state.map((todo) =>
todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
);
case 'DELETE_TODO':
return state.filter((todo) => todo.id !== action.payload);
default:
return state;
}
};
Penjelasan:
initialTodosState: Ini data awal to-do kita.todoReducer: Fungsi reducer yang nerimastatedanaction. Ada 3 tipeactionyang kita support:ADD_TODO,TOGGLE_TODO, danDELETE_TODO. Jangan lupa, reducer harus selalu mengembalikan state baru, jangan memodifikasi state lama secara langsung (mutating state) ya, gaes! Itu best practice-nya.
Langkah 2: Bikin Context dan Provider (src/context/TodoContext.jsx)
Ini adalah "jembatan" yang akan menghubungkan state dan dispatch ke seluruh komponen.
// src/context/TodoContext.jsx
import React, { createContext, useReducer } from 'react';
import { initialTodosState, todoReducer } from './todoReducer';
// 1. Bikin Context-nya
// Kita bikin dua Context: satu buat state, satu buat dispatch.
// Ini best practice biar komponen anak yang cuma butuh dispatch nggak ikutan re-render
// ketika state berubah, dan sebaliknya.
export const TodoStateContext = createContext(undefined);
export const TodoDispatchContext = createContext(undefined);
// 2. Bikin Provider Component-nya
export const TodoProvider = ({ children }) => {
const [todos, dispatch] = useReducer(todoReducer, initialTodosState);
return (
<TodoStateContext.Provider value={todos}>
<TodoDispatchContext.Provider value={dispatch}>
{children}
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
);
};
Penjelasan:
- Kita bikin dua
Context:TodoStateContextbuat statetodosdanTodoDispatchContextbuat fungsidispatch. Ini penting banget buat optimasi performa! TodoProvideradalah komponen yang membungkus aplikasi kita. Di dalamnya,useReducerdipanggil dantodos(state) sertadispatch(fungsi untuk mengubah state) disediakan kechildren-nya.
Langkah 3: Gabungin di App.jsx
Sekarang, kita pasang TodoProvider di level teratas aplikasi kita agar semua komponen di bawahnya bisa mengakses state dan dispatch.
// src/App.jsx
import React from 'react';
import { TodoProvider } from './context/TodoContext';
import AddTodoForm from './components/AddTodoForm';
import TodoList from './components/TodoList';
function App() {
return (
<TodoProvider>
<div style={{ maxWidth: '600px', margin: '50px auto', padding: '20px', border: '1px solid #eee', borderRadius: '8px' }}>
<h1>My To-Do List Gaes!</h1>
<AddTodoForm />
<TodoList />
</div>
</TodoProvider>
);
}
export default App;
Penjelasan:
Appkita sekarang dibungkus samaTodoProvider. Artinya,AddTodoFormdanTodoList(serta komponen di dalamnya) bisa langsung ngambil state dan dispatch dari context.
Langkah 4: Konsumsi State dan Dispatch di Komponen Anak
Sekarang giliran komponen AddTodoForm, TodoList, dan TodoItem ngambil data dan fungsi dispatch dari Context.
src/components/AddTodoForm.jsx
// src/components/AddTodoForm.jsx
import React, { useState, useContext } from 'react';
import { TodoDispatchContext } from '../context/TodoContext';
function AddTodoForm() {
const [newTodoText, setNewTodoText] = useState('');
const dispatch = useContext(TodoDispatchContext); // Ambil dispatch dari Context
const handleSubmit = (e) => {
e.preventDefault();
if (newTodoText.trim() === '') return;
dispatch({ type: 'ADD_TODO', payload: newTodoText }); // Dispatch action ADD_TODO
setNewTodoText('');
};
return (
<form onSubmit={handleSubmit} style={{ marginBottom: '20px' }}>
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="Tambah to-do baru..."
style={{ padding: '8px', marginRight: '10px', width: '70%' }}
/>
<button type="submit" style={{ padding: '8px 15px', cursor: 'pointer' }}>
Tambah
</button>
</form>
);
}
export default AddTodoForm;
Penjelasan:
- Kita cuma butuh
dispatchdi sini, jadi kitauseContext(TodoDispatchContext). - Saat form di-submit, kita
dispatchactionADD_TODOdengannewTodoTextsebagaipayload-nya.
src/components/TodoItem.jsx
// src/components/TodoItem.jsx
import React, { useContext } from 'react';
import { TodoDispatchContext } from '../context/TodoContext';
function TodoItem({ todo }) {
const dispatch = useContext(TodoDispatchContext); // Ambil dispatch dari Context
const handleToggle = () => {
dispatch({ type: 'TOGGLE_TODO', payload: todo.id }); // Dispatch TOGGLE_TODO
};
const handleDelete = () => {
dispatch({ type: 'DELETE_TODO', payload: todo.id }); // Dispatch DELETE_TODO
};
return (
<li style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px', padding: '10px', border: '1px solid #f0f0f0', borderRadius: '4px', textDecoration: todo.completed ? 'line-through' : 'none' }}>
<span onClick={handleToggle} style={{ cursor: 'pointer' }}>
{todo.text}
</span>
<button onClick={handleDelete} style={{ background: 'salmon', color: 'white', border: 'none', padding: '5px 10px', borderRadius: '4px', cursor: 'pointer' }}>
X
</button>
</li>
);
}
export default TodoItem;
Penjelasan:
- Sama kayak
AddTodoForm, kita cuma butuhdispatch. handleToggledanhandleDeleteakandispatchaction yang sesuai.
src/components/TodoList.jsx
// src/components/TodoList.jsx
import React, { useContext } from 'react';
import { TodoStateContext } from '../context/TodoContext';
import TodoItem from './TodoItem';
function TodoList() {
const todos = useContext(TodoStateContext); // Ambil state todos dari Context
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
export default TodoList;
Penjelasan:
- Di sini, kita hanya butuh state
todos, jadi kitauseContext(TodoStateContext). - List to-do akan di-render berdasarkan data
todosyang diambil dari Context.
Gimana, gaes? Simpel tapi powerful banget, kan? Nggak ada lagi tuh props drilling yang bikin mumet kepala!
Tips Praktis Biar Makin GG (Good Game)!
- Struktur Folder yang Rapi: Untuk project kecil, satu folder
contextcukup. Kalau udah gede, bisa pisahin jadistore/reducers,store/actions,store/contexts. Penting banget biar nggak gampang nyasar. - Split Context (State dan Dispatch): Kita udah lakuin di contoh di atas! Ini best practice paling penting buat performa. Kenapa? Kalau kita gabungin
statedandispatchdi satu context, setiap kalistateberubah, semua komponen yang pake context itu bakal re-render, bahkan yang cuma butuhdispatchdoang. Dengan dipisah, komponen yang cuma butuhdispatchnggak akan re-render kalau cumastateyang berubah, karena fungsidispatchitu stabil (tidak berubah antar render). - Memoisasi Provider (Opsional, tapi Bagus): Kalau value yang kamu kasih ke
Provideritu objek atau array yang dibuat ulang setiap render, bisa jadi masalah performa. Kamu bisa bungkusProviderkamu pakeuseMemoatauuseCallbackbuat value yang lebih kompleks, tapi untuk kasus kita tadi (state array dan dispatch function), React udah cukup pinter kok. - Gunakan Konstan untuk Action Types: Daripada nulis
type: 'ADD_TODO'terus-terusan, lebih baik definisikan konstanconst ADD_TODO = 'ADD_TODO';di satu file, terus diimpor. Ini biar nggak ada typo dan lebih gampang di-refactor. - Perhatikan Kapan Harus Context, Kapan Props Biasa: Context API itu nggak selalu jadi solusi buat semua masalah. Kalau datanya cuma butuh di-pass ke satu atau dua level komponen di bawah, props biasa mungkin lebih simpel dan mudah dipahami. Jangan over-engineering ya!
Kesimpulan: Bye-bye Props Drilling!
Nah, itu dia gaes, panduan lengkap langkah demi langkah buat ngelola state di React pake kombinasi Context API dan useReducer. Dengan duo maut ini, kamu bisa bikin aplikasi React yang punya state management rapi, mudah di-maintain, dan performanya oke.
Nggak ada lagi alasan buat pusing sama props drilling atau state yang berantakan. Sekarang kamu udah punya tools yang super keren di tangan! Yuk, langsung dicoba di project kamu selanjutnya biar makin jago! Kalau ada pertanyaan, jangan sungkan spill di kolom komentar ya! Happy coding, ngab!
Berikan Rating
Komentar (0)
Silakan login untuk memberikan komentar.
Login SekarangKata Kunci
Belum ada komentar. Jadilah yang pertama!