Efek Samping Terkendali: Memahami Hook useEffect untuk Logika Kompleks React (Part 10)

Rifqi An Rifqi An
Maret 04, 2026


Efek Samping Terkendali: Memahami Hook useEffect untuk Logika Kompleks React (Part 10)

Pernahkah kamu merasa proyek React-mu mulai penuh dengan logika yang bikin pusing? Data fetching di sana, manipulasi DOM di sini, event listener yang nongol tiba-tiba... rasanya kayak lagi jadi pawang singa, berusaha menenangkan efek-efek samping yang liar! Nah, kalau kamu udah nyampe di Part 10 seri tutorial React kita ini, berarti kamu siap kenalan sama useEffect, hook paling powerful (dan sering bikin pusing) untuk mengelola semua 'efek samping' itu biar tetap terkendali. Siap ngopi dan menyelami lautan logika? Yuk!

Daftar Isi

Apa Itu useEffect? Si Penangkal Efek Samping

Di dunia React, kita mengenal side effects (efek samping) sebagai segala sesuatu yang terjadi "di luar" render murni komponen. Contohnya? Mengambil data dari API (data fetching), memanipulasi DOM secara langsung (walaupun jarang direkomendasikan), mengatur event listener, atau mungkin mengatur timer. Dulu, di kelas komponen (class components), semua ini ditangani di componentDidMount, componentDidUpdate, dan componentWillUnmount. Ribet, kan?

Nah, useEffect hadir sebagai jurus pamungkas untuk menggantikan semua itu. Dia memungkinkan kita melakukan efek samping setelah setiap render, atau bahkan hanya pada render pertama, atau ketika nilai-nilai tertentu berubah. Intinya, useEffect adalah tempat parkir aman untuk semua logika yang "berinteraksi" dengan dunia luar komponen React kita.

Struktur Dasar useEffect: The Holy Trinity

Secara garis besar, useEffect itu punya dua bagian utama (tapi seringkali tiga):

  1. Fungsi Efek: Ini adalah fungsi yang akan dijalankan. Di sinilah semua logika efek samping kamu ditaruh.
  2. Array Dependensi (Dependency Array): Opsional, tapi krusial! Ini semacam "daftar pengawas" yang memberitahu React kapan harus menjalankan ulang fungsi efekmu.
  3. (Opsional) Fungsi Cleanup: Ini akan dibahas di bagian selanjutnya, tapi penting banget!

Oke, mari kita lihat kodingan sederhana biar makin jelas:


import React, { useState, useEffect } from 'react';

function KomponenSederhana() {
  const [count, setCount] = useState(0);

  // Ini adalah useEffect yang akan berjalan setelah SETIAP render
  useEffect(() => {
    console.log('Komponen ini baru saja di-render atau di-update! Count:', count);
    // Contoh sederhana: Mengupdate judul halaman
    document.title = `Kamu klik ${count} kali`;
  }); // Perhatikan: tidak ada array dependensi di sini!

  return (
    <div>
      <p>Kamu sudah klik {count} kali.</p>
      <button onClick={() => setCount(count + 1)}>
        Klik Aku!
      </button>
    </div>
  );
}

Di contoh atas, karena tidak ada dependency array (alias array kosong di parameter kedua), fungsi di dalam useEffect akan berjalan setiap kali komponen KomponenSederhana di-render ulang. Ini bisa jadi performance killer kalau efekmu berat!

Misteri Array Dependensi: Kapan Dia Lari, Kapan Dia Anteng?

Ini dia bagian yang paling sering bikin ngopi sampai lembur: dependency array. Ini adalah parameter kedua opsional dari useEffect yang menentukan kapan efekmu harus dijalankan ulang. Ibaratnya, ini adalah daftar "pengawas" React. Kalau ada nilai di daftar itu yang berubah antara dua render, maka efek akan dijalankan ulang. Kalau nggak ada yang berubah, dia anteng aja.

1. Array Kosong ([]): Hanya Jalan Sekali!

Kalau kamu ngasih array kosong, useEffect hanya akan jalan sekali setelah initial render. Mirip componentDidMount. Cocok banget buat data fetching awal atau event listener global.


import React, { useState, useEffect } from 'react';

function KomponenLoadingData() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Fungsi ini hanya akan dijalankan SEKALI setelah komponen pertama kali di-render
    console.log('Mulai fetching data...');
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => {
        setData(json);
        setLoading(false);
      })
      .catch(err => {
        setError('Gagal mengambil data, mungkin internetmu lagi ngambek?');
        setLoading(false);
        console.error('Error fetching data:', err);
      });
  }, []); // <-- Perhatikan, ada array kosong di sini!

  if (loading) return <p>Lagi loading nih, sabar ya...</p>;
  if (error) return <p style={{ color: 'red' }}><strong>ERROR:</strong> {error}</p>;

  return (
    <div>
      <h3>Data berhasil diambil!</h3>
      <p><strong>Judul:</strong> {data.title}</p>
      <p><strong>Status:</strong> {data.completed ? 'Selesai' : 'Belum Selesai'}</p>
    </div>
  );
}

2. Array dengan Nilai ([dep1, dep2]): Hanya Berjalan Jika Berubah

Ini yang paling umum dipakai. Kamu memasukkan variabel, state, atau props yang ingin kamu "awasi". Kalau salah satu dari mereka berubah, useEffect akan jalan lagi. Ini penting banget buat performa dan mencegah bug aneh.


import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    // Kita akan fetch data user setiap kali userId berubah
    console.log(`Mengambil data untuk User ID: ${userId}`);
    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then(response => response.json())
      .then(json => {
        setUser(json);
        setLoading(false);
      });
    // <-- Penting! userId adalah dependensi di sini
  }, [userId]); // <-- Effect ini akan jalan ulang jika nilai userId berubah

  if (loading) return <p>Loading profil user...</p>;
  if (!user) return <p>User tidak ditemukan, mungkin kamu salah ketik ID-nya?</p>;

  return (
    <div>
      <h3>Profil Pengguna</h3>
      <p><strong>Nama:</strong> {user.name}</p>
      <p><strong>Email:</strong> {user.email}</p>
      <p><strong>Telepon:</strong> {user.phone}</p>
    </div>
  );
}

// Cara menggunakan:
// <UserProfile userId={1} />
// <UserProfile userId={2} /> // Ini akan memicu effect untuk dijalankan ulang

Hati-hati, lho! Kalau kamu lupa masukkin dependensi yang penting, bisa-bisa data yang ditampilkan jadi stale (basi) atau bahkan bikin bug yang susah dilacak. React akan "ngomel" di konsol kalau kamu ngoding pake ESLint dan punya missing dependency, jadi patuhi sarannya ya!

Fungsi Cleanup: Sang Penyelamat dari Memory Leak

Kadang, efek samping kita nggak cuma perlu dimulai, tapi juga perlu "dibersihkan" saat komponen tidak lagi dipakai atau saat dependensinya berubah. Contohnya? Menghapus event listener, membatalkan langganan (subscription), atau menghapus timer. Kalau nggak dibersihin, bisa-bisa terjadi memory leak atau efek yang jalan di latar belakang padahal komponennya udah nggak ada. Ini dia peran dari cleanup function!

useEffect bisa mengembalikan sebuah fungsi. Fungsi yang dikembalikan ini akan dijalankan saat komponen unmount (dihapus dari DOM), atau sebelum efek dijalankan ulang karena dependensinya berubah. Mirip componentWillUnmount.


import React, { useState, useEffect } from 'react';

function KomponenTimer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // Memulai timer
    const intervalId = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);
    console.log('Timer dimulai!');

    // Fungsi cleanup: akan dijalankan saat komponen di-unmount atau
    // sebelum effect ini dijalankan ulang (jika ada dependensi)
    return () => {
      clearInterval(intervalId); // Membersihkan timer
      console.log('Timer dihentikan dan dibersihkan!');
    };
  }, []); // Array kosong berarti timer hanya akan dimulai sekali dan dibersihkan saat unmount

  return (
    <div>
      <p>Waktu berjalan: {seconds} detik</p>
    </div>
  );
}

// Untuk mencoba cleanup function, kamu bisa membuat komponen induk
// yang bisa me-mount dan unmount KomponenTimer ini (misal pakai conditional rendering)

Tanpa cleanup function, timer di atas akan terus berjalan bahkan setelah KomponenTimer tidak lagi ada di layar, menghabiskan sumber daya dan berpotensi menyebabkan error. Jangan sampai lupain ini ya, penting banget!

Mengelola Logika Kompleks dengan useEffect

Untuk logika yang lebih kompleks, kita bisa memakai useEffect berkali-kali dalam satu komponen. Jangan takut! Justru, memisahkan efek yang berbeda ke dalam useEffect yang berbeda (dengan dependensi yang berbeda pula) akan membuat kodemu lebih rapi dan mudah di-debug. Setiap useEffect harusnya fokus pada satu jenis efek samping.

Misalnya, kamu punya komponen yang harus:

  1. Mengambil data user awal saat komponen dimuat.
  2. Mengupdate judul halaman berdasarkan nama user.
  3. Mendaftarkan event listener untuk tombol tertentu.

Ini bisa dihandle dengan tiga useEffect terpisah:


import React, { useState, useEffect } from 'react';

function DashboardPengguna({ userId }) {
  const [user, setUser] = useState(null);
  const [pesanNotifikasi, setPesanNotifikasi] = useState('');

  // Efek 1: Mengambil data user (hanya saat userId berubah)
  useEffect(() => {
    console.log('Fetching user data...');
    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then(response => response.json())
      .then(data => setUser(data))
      .catch(error => console.error('Error fetching user:', error));
  }, [userId]); // Dependensi: userId

  // Efek 2: Mengupdate judul halaman (hanya saat user.name berubah)
  useEffect(() => {
    if (user) {
      document.title = `Dashboard: ${user.name}`;
    } else {
      document.title = 'Dashboard Pengguna';
    }
  }, [user]); // Dependensi: user

  // Efek 3: Menangani event click pada button (hanya sekali saat mount & cleanup saat unmount)
  useEffect(() => {
    const handleButtonClick = () => {
      setPesanNotifikasi(`Halo, ${user ? user.name : 'Pengguna'}!`);
      alert(`Button diklik oleh ${user ? user.name : 'Anonim'}!`);
    };

    const myButton = document.getElementById('notifButton');
    if (myButton) {
      myButton.addEventListener('click', handleButtonClick);
      console.log('Event listener ditambahkan.');
    }

    return () => {
      if (myButton) {
        myButton.removeEventListener('click', handleButtonClick);
        console.log('Event listener dihapus.');
      }
    };
  }, [user]); // Dependensi: user (karena handleButtonClick pakai user)

  return (
    <div>
      <h2>Selamat datang di Dashboard, {user ? user.name : 'Pengguna'}!</h2>
      {user ? (
        <p>Email Anda: {user.email}</p>
      ) : (
        <p>Memuat data pengguna...</p>
      )}
      <button id="notifButton">Kirim Notifikasi</button>
      {pesanNotifikasi &amp;&amp; <p><em>{pesanNotifikasi}</em></p>}
    </div>
  );
}

Keren kan? Dengan memisahkan logika ke useEffect yang berbeda, kita bisa mengelola dependensi dan cleanup-nya masing-masing, bikin code lebih modular dan gampang diurus pas ada bug. Jangan digabung jadi satu useEffect doang ya, nanti malah pusing sendiri!

Ingat, kalau kamu ngoding dan menemukan perilaku aneh, atau data yang nggak sinkron, coba cek lagi dependency array kamu. Kemungkinan besar ada yang terlewat atau salah penempatan. Ini adalah sumber bug paling umum di useEffect, bahkan programmer veteran pun kadang kejebak!

Latihan: Petualangan si Komponen Gabut

Oke, sekarang giliranmu biar nggak cuma baca doang! Bayangkan kamu punya sebuah aplikasi "Pantau Gabut" untuk anak-anak IT yang sering nge-scroll media sosial. Setiap kali ada perubahan status gabut (misal: "Gabut level 1", "Gabut Akut", "Nyaris Overthinking"), kamu perlu:

  1. Mengirim sinyal ke "Server Gabut" (simulasikan dengan console.log atau alert).
  2. Mengupdate judul halaman browser menjadi "Status Gabut: [Status Terkini]".
  3. Jika status gabutnya "Nyaris Overthinking", secara otomatis jalankan timer 5 detik, setelah itu tampilkan notifikasi "Waktunya Ngopi!". Timer ini harus di-reset jika status gabut berubah sebelum 5 detik.
  4. Jika komponen "Pantau Gabut" ini tidak lagi dipakai, pastikan semua timer dihentikan dan tidak ada lagi notifikasi yang nongol.

Yuk, implementasikan dengan useState dan useEffect yang kamu pelajari barusan! Jangan lupa cleanup function-nya ya, biar nggak ada timer ghost yang gentayangan!

Semangat ngoding, teman-teman! useEffect ini memang terlihat ribet di awal, tapi begitu kamu paham logikanya, dia akan jadi sahabat terbaikmu untuk mengelola efek samping di React. Di Part 11 nanti, kita mungkin akan coba bahas custom hooks yang akan sangat berguna untuk me-refactor useEffect kompleks ini menjadi lebih rapi!

Bagikan Artikel Ini