useEffect Lanjutan — React

Pas pelajaran sebelumnya kamu udah paham useEffect buat side effect. Tapi ada KILLER feature yang sering kelewat: cleanup function. Bayangin user buka 5 modal b

Pas pelajaran sebelumnya kamu udah paham useEffect buat side effect. Tapi ada KILLER feature yang sering kelewat: cleanup function. Bayangin user buka 5 modal beruntun, tiap modal pasang event listener keydown (deteksi ESC). Tanpa cleanup, listener nempel terus walaupun modal udah ketutup — 5 modal = 5 listener nyangkut, klik ESC sekali jalan 5 kali. Memory leak. Performance drop. Bug aneh muncul. Cleanup function = janji ke React: "kalau effect ini gak relevan lagi, jalanin instruksi ini buat bersihin diri".

💡 Aturan emas. Tiap effect yang BIKIN sesuatu yang persist di luar component (timer, listener, subscription, animation, koneksi WebSocket) WAJIB punya cleanup. Kalau gak, kamu lagi bikin memory leak yang bom waktu — gak kelihatan di dev, nyebelin di production.

Cleanup Function

Return dari effect = cleanup function. React panggil saat:

  1. Component unmount (dilepas dari DOM)
  2. Sebelum effect re-run (deps berubah, effect lama harus cleanup dulu)
function Timer() {
  const [seconds, setSeconds] = useState(0)

  useEffect(() => {
    const id = setInterval(() => {
      setSeconds(s => s + 1)
    }, 1000)

    return () => clearInterval(id)  // ← cleanup
  }, [])

  return <p>{seconds} detik</p>
}

Lifecycle Pattern

useEffect(() => {
  // SETUP — jalan saat mount atau deps berubah
  console.log("Setup")
  const subscription = subscribeToWebSocket()

  return () => {
    // CLEANUP — jalan saat unmount atau sebelum re-run
    console.log("Cleanup")
    subscription.close()
  }
}, [url])  // re-run kalau url berubah → cleanup lama dulu, baru setup baru

Race Condition di Fetch — Pattern Wajib

Bug klasik: fetch user 1 → user pindah cepat ke user 2 → fetch user 2. Response user 1 dateng BELAKANGAN → setUser(user1) override user2. UI nampilin user 1 padahal harusnya user 2. Solusi: cancellation flag.

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

  useEffect(() => {
    let cancelled = false

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data)
      })

    return () => { cancelled = true }
  }, [userId])

  if (!user) return <p>Loading...</p>
  return <h2>{user.name}</h2>
}

Modern: AbortController

useEffect(() => {
  const controller = new AbortController()

  fetch(`/api/users/${userId}`, { signal: controller.signal })
    .then(res => res.json())
    .then(setUser)
    .catch(err => {
      if (err.name !== "AbortError") console.error(err)
    })

  return () => controller.abort()
}, [userId])

Subscribe to Window Event

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth)
  window.addEventListener("resize", handleResize)

  return () => window.removeEventListener("resize", handleResize)
}, [])

🎭 Analogi sehari-hari

Cleanup itu kayak bayar listrik kamar kost. Tiap bulan kamu nyewa kamar (effect setup) — pasang lampu, AC, kulkas. Pas pindah keluar (component unmount), kamu WAJIB matiin semua, buang sampah, balikin kunci (cleanup). Kalau gak — listrik tetep jalan di kamar kosong, sewa numpuk gak kebayar, pemilik kost-nya rugi. React pemilik kost yang strict — tiap effect yang setup sesuatu, WAJIB ada janji buat cleanup. Kalau lupa, bug-nya gak immediately kelihatan tapi efeknya numpuk: memory bocor, performa drop, listener listener berlapis-lapis.

⚠️ Jebakan yang sering ditemui

🎯 Punya cleanup atau enggak?

  • setInterval/setTimeout → wajib clearInterval/clearTimeout
  • addEventListener (window/document/element) → wajib removeEventListener
  • WebSocket / EventSource → wajib .close()
  • Fetch → AbortController atau cancelled flag
  • Subscribe to library (Firebase, Pusher) → call .unsubscribe() yang library kasih
  • Update document.title / DOM → biasanya GAK perlu cleanup (browser auto-revert next setup)
  • Log/console → gak butuh cleanup

Aturan: kalau effect setup sesuatu yang HIDUP di luar React (browser API, network, library), WAJIB cleanup.

TL;DR: Cleanup function = return dari useEffect, jalan saat unmount atau sebelum effect re-run. Wajib buat setInterval/setTimeout/addEventListener/WebSocket/fetch (race condition). Pakai AbortController modern atau cancelled flag untuk fetch. Strict Mode dev jalanin effect 2x — bukan bug, deteksi cleanup proper. Async langsung di useEffect = ERROR (return cleanup, bukan Promise). Modern alternatif: TanStack Query handle race condition + cancellation otomatis.

Yang akan kamu pelajari