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.

💡 Semua contoh kode di artikel ini diambil dari project nyata yang pernah saya review atau kerjakan. Nama dan konteks sudah diubah untuk privasi.

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 — Jangan lakukan ini Langsung mengubah array/object di state
JSX TodoList.jsx
// ❌ 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 — Gunakan pendekatan ini Selalu buat copy baru dari state
JSX TodoList.jsx
// ✅ 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 — Dependency tidak lengkap
JSX UserProfile.jsx
// ❌ 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!
JSX UserProfile.jsx — Fixed
// ✅ 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
⚠️ Aturan praktis: Install ESLint plugin 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.

JSX ProductList.jsx
// ❌ 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.

JSX Timer.jsx
// ❌ 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.

JSX App.jsx → Solution with Context
// ❌ 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} />;
}
Kapan pakai Context vs Zustand/Redux? Context cukup untuk state yang jarang berubah (theme, user auth, locale). Untuk state yang sering berubah atau complex, gunakan Zustand atau Redux Toolkit agar performa lebih optimal.

Kesimpulan: Checklist Sebelum Push ke Production

  • ✅ Tidak ada state.push(), state.x = y langsung
  • ✅ Semua nilai yang dipakai di useEffect masuk dependency array
  • ✅ Semua list menggunakan key yang 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
💡 Key Takeaway

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 →