Saya sudah melakukan code review ratusan PR selama karier saya sebagai Tech Lead. Dan hampir selalu, kesalahan yang sama terus berulang dari developer junior ke developer junior lainnya.
Artikel ini bukan untuk menghakimi — justru sebaliknya. Saya sendiri pernah melakukan semua kesalahan ini. Tujuannya satu: agar kamu tidak perlu belajar dari kesalahan yang sama dan bisa langsung menulis kode React yang production-ready.
01 Mutating State Secara Langsung
Ini adalah kesalahan paling fundamental dan sayangnya juga paling umum. React bekerja dengan prinsip immutability — state tidak boleh diubah langsung, melainkan harus diganti dengan nilai baru.
// ❌ SALAH: Mutating state langsung const [todos, setTodos] = useState([]); const addTodo = (newTodo) => { todos.push(newTodo); // ⚠️ Langsung mutate array! setTodos(todos); // React tidak tahu state berubah }; const updateTodo = (id, text) => { const todo = todos.find(t => t.id === id); todo.text = text; // ⚠️ Mutate object langsung! setTodos([...todos]); };
// ✅ BENAR: Selalu return state baru const [todos, setTodos] = useState([]); const addTodo = (newTodo) => { setTodos(prev => [...prev, newTodo]); // spread copy }; const updateTodo = (id, text) => { setTodos(prev => prev.map(todo => todo.id === id ? { ...todo, text } // spread copy object : todo ) ); };
Mengapa ini kritis? React menggunakan shallow comparison untuk mendeteksi perubahan state. Ketika kamu mutate state langsung, reference-nya tidak berubah — React mengira tidak ada yang berubah dan skip re-render. Hasilnya: UI tidak update, bug yang sulit di-debug.
02 useEffect dengan Dependency yang Salah
Kesalahan useEffect adalah topik paling sering muncul di code review saya. Ada dua pola salah
yang paling umum: dependency kosong yang misleading dan dependency yang menyebabkan
infinite loop.
// ❌ SALAH 1: userId dipakai tapi tidak masuk dep array useEffect(() => { fetchUser(userId); // userId dari props/state }, []); // ⚠️ Hanya run sekali, tidak update! // ❌ SALAH 2: Object/array di dependency → infinite loop! const config = { limit: 10 }; // baru tiap render useEffect(() => { fetchData(config); }, [config]); // ⚠️ config baru tiap render → infinite loop!
// ✅ BENAR 1: Semua nilai yang dipakai masuk dependency useEffect(() => { fetchUser(userId); }, [userId]); // Re-run setiap userId berubah // ✅ BENAR 2: Primitif atau useMemo untuk object const config = useMemo( () => ({ limit: 10 }), [] // Hanya dibuat sekali ); useEffect(() => { fetchData(config); }, [config]); // config stabil, tidak infinite loop
eslint-plugin-react-hooks. Rule
exhaustive-deps akan otomatis mendeteksi dependency yang kurang atau berlebihan.
03 Key Prop yang Tidak Unik
Key prop adalah cara React mengidentifikasi setiap elemen dalam sebuah list. Menggunakan index
sebagai key terdengar praktis, tapi bisa menyebabkan bug yang sangat sulit dideteksi,
terutama pada list yang bisa diubah urutan atau dihapus.
// ❌ SALAH: Index sebagai key {products.map((product, index) => ( <ProductCard key={index} product={product} /> // Kalau item pertama dihapus, semua item punya key baru // React akan unmount-remount semua, bukan hanya yang dihapus ))} // ✅ BENAR: Gunakan ID unik dari data {products.map((product) => ( <ProductCard key={product.id} // Stabil & unik product={product} /> ))}
04 Tidak Membersihkan Side Effects
Memory leak adalah masalah serius yang bisa membuat aplikasi lambat atau crash. Ini terjadi ketika kamu lupa membersihkan subscription, timer, atau async operation di useEffect.
// ❌ SALAH: Timer tidak dibersihkan useEffect(() => { const interval = setInterval(() => { setCount(c => c + 1); }, 1000); // ⚠️ Tidak ada return cleanup → memory leak! }, []); // ✅ BENAR: Selalu return cleanup function useEffect(() => { const interval = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(interval); // ✅ Cleanup! }, []); // ✅ Cleanup untuk fetch (abort controller) useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }) .then(r => r.json()) .then(setData); return () => controller.abort(); // ✅ Cancel fetch! }, [url]);
05 Prop Drilling yang Berlebihan
Prop drilling adalah ketika kamu meneruskan props melewati 3+ layer komponen hanya untuk sampai ke komponen yang benar-benar membutuhkannya. Ini membuat kode sulit dibaca, di-maintain, dan di-test.
// ❌ SALAH: Prop drilling 4 layer <App user={user}> <Layout user={user}> // Layout tidak butuh user <Sidebar user={user}> // Sidebar tidak butuh user <Avatar user={user} /> // Ini yang butuh user // ✅ BENAR: Gunakan Context API const UserContext = createContext(); function App() { return ( <UserContext.Provider value={user}> <Layout /> // Tidak perlu meneruskan user </UserContext.Provider> ); } function Avatar() { const user = useContext(UserContext); // ✅ Langsung ambil return <img src={user.avatar} />; }
Kesimpulan: Checklist Sebelum Push ke Production
- ✅ Tidak ada
state.push(),state.x = ylangsung - ✅ Semua nilai yang dipakai di useEffect masuk dependency array
- ✅ Semua list menggunakan
keyyang stabil dan unik - ✅ Setiap useEffect yang punya side effect punya cleanup function
- ✅ Prop tidak di-drill lebih dari 2 level — gunakan Context atau state manager
React adalah library yang powerful, tapi mudah disalahgunakan jika tidak memahami fundamental-nya. Lima kesalahan di atas bukan hanya soal "cara yang benar" — mereka langsung berdampak pada performa, maintainability, dan bug rate aplikasimu. Investasikan waktu untuk memahami, bukan hanya menghafal.
Kalau kamu mau belajar React dengan benar dari praktisi yang sudah membangun 30+ project nyata, saya buka kelas privat dan bootcamp. Materi yang diajarkan persis seperti ini — dari project production, bukan dari tutorial.
🎓 Daftar Kelas Sekarang →