Lompat ke konten
Kembali ke modul

React: Membangun Antarmuka Pengguna · 6/9

useEffect — dan kapan justru jangan dipakai

Sebagian besar effect di kode junior seharusnya tidak ada. Pelajari sedikit kasus yang memang perlu.

Baca 32 menit

Setelah pelajaran ini kamu bisa

  • Menyebutkan effect itu untuk apa, dan bukan untuk apa
  • Menulis dependency array dengan benar
  • Selalu membersihkan subscription, timer, atau listener
  • Mengenali empat effect yang seharusnya tidak ada

useEffect ada untuk satu tujuan: menyinkronkan dengan sesuatu di luar React. Sebuah subscription, timer, API browser, atau widget pihak ketiga. Kalau pekerjaannya bukan tentang dunia luar, hampir pasti ia tidak seharusnya berada di dalam effect.

tsx
import { useEffect, useState } from "react";

function WindowWidth() {
  const [width, setWidth] = useState(0);

  useEffect(() => {
    // 1. The effect: connect to the outside world.
    function handleResize() {
      setWidth(window.innerWidth);
    }
    handleResize();                                  // set the initial value
    window.addEventListener("resize", handleResize);

    // 2. The cleanup: disconnect. React calls this before re-running,
    //    and when the component unmounts.
    return () => window.removeEventListener("resize", handleResize);

    // 3. The dependency array: when should this re-run?
  }, []);   // [] = only on mount, because it depends on nothing

  return <p>{width}px</p>;
}

Dependency array

Ditulis sebagaiDijalankan
, [])Sekali, setelah mount
, [venueId])Setelah mount, dan setiap kali venueId berubah
) — dihilangkan sama sekaliSetelah setiap render. Hampir selalu sebuah bug.

React membandingkan dependency dengan Object.is — kesetaraan reference, pemeriksaan yang sama seperti hal lainnya. Itu langsung mengarah ke bug infinite loop yang dialami semua orang sekali.

Coba sendiri

Kenapa object atau array di dalam dependency array membuat effect-nya jalan terus-menerus.

Hasil

Tekan Jalankan untuk melihat hasilnya.

Ini jalan di browser kamu, di dalam sandbox. Apa pun yang kamu tulis di sini tidak bisa merusak situs.

Cleanup bukan opsional

Setiap subscription yang dibuka sebuah effect harus ditutup. Ini pelajaran kebocoran memori dari modul 3, dan React memberimu tempat yang tepat untuk melakukannya.

tsx
// Timers
useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
}, []);

// Listeners - the same function reference, as module 3 explained
useEffect(() => {
  const onKey = (e) => { if (e.key === "Escape") close(); };
  document.addEventListener("keydown", onKey);
  return () => document.removeEventListener("keydown", onKey);
}, [close]);

// Fetching: cancel the result of a request that is no longer wanted
useEffect(() => {
  let active = true;
  loadVenue(venueId).then((data) => {
    if (active) setVenue(data);      // ignore a response that arrived late
  });
  return () => { active = false; };
}, [venueId]);
Pola terakhir itu mencegah bug nyata: berpindah venue dengan cepat dan response pertama yang lambat bisa datang setelah yang kedua, menimpa data yang benar. Proyek ini memakai persis ini di LessonCompleteButton.

Empat effect yang seharusnya tidak ada

tsx
// 1. Deriving a value. Just calculate it during render.
useEffect(() => { setFullName(first + " " + last); }, [first, last]);   // no
const fullName = first + " " + last;                                    // yes

// 2. Resetting state when a prop changes. Use a key instead.
useEffect(() => { setDraft(""); }, [userId]);              // no
<ProfileForm key={userId} />                              // yes

// 3. Responding to an event. Put it in the handler.
useEffect(() => { if (submitted) sendAnalytics(); }, [submitted]);   // no
function handleSubmit() { sendAnalytics(); save(); }                 // yes

// 4. Fetching on mount in a Next.js app. Fetch on the server.
useEffect(() => { fetch("/api/venues").then(setVenues); }, []);   // usually no
// A Server Component just awaits the data - the next module covers this.

Uji yang perlu kamu terapkan: apakah ini menyinkronkan dengan sesuatu di luar React? Sebuah perhitungan tidak. Merespons sebuah klik juga tidak — handler klik-nya sudah ada dan jalan tepat di saat yang benar. Hanya koneksi ke dunia luar yang sungguhan yang memenuhi syarat.

Tugas praktik

Di proyek ini, temukan setiap useEffect — hanya ada empat, di theme-toggle.tsx, code-runner.tsx, dan lesson-complete-button.tsx. Untuk masing-masingnya, jawab: ia menyinkronkan dengan hal luar apa, apa dependency-nya, dan apakah ia melakukan cleanup? Lalu periksa apakah ada di antaranya yang bisa dihapus dengan aturan di atas.

Hasil yang diharapkan

Sebuah media query, sebuah worker, dan satu pembacaan dari server — semuanya benar-benar di luar React.

Cek pemahaman

Mana di antara ini yang benar-benar butuh useEffect?

Pilih jawaban dulu