ReactJS

Belum masuk. Progresmu tidak tersimpan.

Masuk
  • Bab 6 dari 10
  • 30 menit
  • 1 latihan kode
  • 4 soal kuis

useEffect dan useContext: efek samping dan data bersama

Di bab ini (7 bagian)
  1. Tujuan
  2. Render harus murni, efek di luar render
  3. Dependency array: kapan efek dijalankan
  4. Cleanup: membereskan sebelum pergi
  5. Kesalahan umum
  6. Prop drilling dan useContext
  7. Rangkuman

Tujuan bab ini

  • Memahami apa itu efek samping dan menjalankannya dengan useEffect.
  • Mengatur kapan efek berjalan lewat dependency array (tanpa array, [], atau [nilai]) dan membersihkannya dengan cleanup.
  • Mengenali kesalahan umum: infinite loop, dependency yang hilang, dan efek yang jalan dua kali di StrictMode.
  • Berbagi data ke banyak komponen dengan createContext, Provider, dan useContext tanpa prop drilling.

Render harus murni, efek di luar render

Di bab sebelumnya kamu tahu bahwa komponen dipanggil ulang setiap render. Fungsi komponen sebaiknya murni: menerima props dan state, mengembalikan JSX, tanpa menyentuh dunia luar. Tapi aplikasi nyata butuh menyentuh dunia luar: memanggil API, memasang timer, membaca localStorage, mengganti judul tab browser, atau memasang event listener di window. Semua itu disebut efek samping (side effect).

Kalau kamu menaruh efek samping langsung di badan komponen, efek itu jalan di setiap render, bahkan sebelum tampilannya muncul di layar. useEffect adalah tempat resminya: kode di dalamnya dijalankan React setelah render selesai dan perubahan sudah diterapkan ke DOM.

import { useEffect, useState } from "react";

function Penghitung() {
  const [angka, setAngka] = useState(0);

  useEffect(() => {
    document.title = `Diklik ${angka} kali`;
  }, [angka]);

  return <button onClick={() => setAngka((n) => n + 1)}>Klik</button>;
}
// Judul tab: "Diklik 0 kali", lalu "Diklik 1 kali", dan seterusnya

Bentuk umumnya: useEffect(fungsiEfek, dependencyArray).

Dependency array: kapan efek dijalankan

Dependency array memberi tahu React nilai apa saja yang dipakai efek. Setelah setiap render, React membandingkan isi array dengan render sebelumnya (pakai Object.is). Kalau ada yang berbeda, efek dijalankan ulang.

BentukKapan efek jalanContoh pemakaian
Tanpa array: useEffect(fn)Setelah setiap renderJarang dipakai, misalnya logging
Array kosong: useEffect(fn, [])Sekali, setelah komponen pertama kali tampil (mount)Memasang timer atau listener sekali
Ada isi: useEffect(fn, [a, b])Saat mount, lalu setiap kali a atau b berubahFetch ulang saat id berubah, update judul

Analoginya alarm di HP. Tanpa array itu alarm yang bunyi setiap kali kamu membuka HP. Array kosong itu alarm sekali saja. Array berisi nilai itu alarm yang bunyi setiap kali jadwal tertentu berubah.

Cleanup: membereskan sebelum pergi

Beberapa efek meninggalkan "sisa": timer yang terus berdetak, listener yang terus mendengar, atau koneksi yang terus terbuka. Kalau komponen sudah hilang dari layar tapi timer-nya masih jalan, terjadi memory leak dan bug aneh. Untuk itu, efek boleh mengembalikan fungsi cleanup.

function Jam() {
  const [waktu, setWaktu] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => {
      setWaktu(new Date());
    }, 1000);

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

  return <h2>{waktu.toLocaleTimeString("id-ID")}</h2>;
}

Urutan kerjanya:

  1. Komponen tampil, React menjalankan efek: interval dipasang.
  2. Setiap detik setWaktu memicu render ulang. Efek tidak jalan lagi karena dependency-nya [].
  3. Saat komponen dilepas (unmount), React memanggil cleanup: interval dihentikan.

Kalau dependency-nya berisi nilai, React menjalankan cleanup dari efek lama sebelum menjalankan efek baru. Jadi selalu ada pasangan pasang-lepas yang rapi.

useEffect(() => {
  function handleResize() {
    setLebar(window.innerWidth);
  }
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

Kesalahan umum

1. Infinite loop. Efek mengubah state, state memicu render, render menjalankan efek lagi, dan seterusnya.

// SALAH: tanpa dependency array, setiap render menambah hitung
useEffect(() => {
  setHitung(hitung + 1);
});

Hal yang sama terjadi bila dependency berisi object atau array yang dibuat ulang di setiap render, misalnya const opsi = { page: 1 } di badan komponen lalu [opsi]. Referensinya selalu baru, jadi efek selalu jalan. Pindahkan object itu ke dalam efek, atau jadikan dependency-nya nilai primitif seperti page.

2. Dependency hilang. Efek memakai kota, tapi dependency-nya []. Akibatnya efek memakai nilai kota dari render pertama selamanya (stale value), dan data tidak ikut berubah saat kota diganti. Aturan praktisnya: semua props, state, dan variabel komponen yang dipakai di dalam efek harus masuk ke dependency array. Plugin ESLint eslint-plugin-react-hooks (aturan exhaustive-deps, sudah ada di template Vite React) akan memberi peringatan bila ada yang terlewat.

3. Efek jalan dua kali di development. Kamu melihat console.log di efek muncul dua kali dan bingung. Ini bukan bug. Template Vite membungkus aplikasi dengan <StrictMode>, dan di mode development React sengaja menjalankan mount, cleanup, mount lagi untuk menguji apakah cleanup-mu benar. Kalau timer-mu jadi dobel, artinya cleanup belum ada. Di build production, efek hanya jalan sekali. Jangan hapus StrictMode untuk "memperbaikinya", perbaiki cleanup-nya.

4. Memakai efek untuk hal yang bisa dihitung. Misalnya menyimpan namaLengkap di state lalu mengisinya lewat efek dari depan dan belakang. Cukup hitung langsung saat render: const namaLengkap = depan + " " + belakang;.

Prop drilling dan useContext

Sekarang masalah lain. Bayangkan tema gelap/terang disimpan di App, tapi yang butuh adalah Tombol yang letaknya App > Halaman > Layout > Sidebar > Tombol. Tanpa alat lain, kamu harus mengoper prop tema melewati Halaman, Layout, dan Sidebar, padahal ketiganya tidak memakainya. Ini disebut prop drilling: kode jadi panjang, dan setiap kali bentuk datanya berubah, banyak file ikut berubah.

Context menyelesaikan ini. Analoginya Wi-Fi: alih-alih menarik kabel LAN ke setiap ruangan, kamu pasang router sekali, dan perangkat mana pun di rumah bisa langsung terhubung.

import { createContext, useContext, useState } from "react";

// 1. Buat context dengan nilai default
const TemaContext = createContext("terang");

// 2. Bungkus pohon komponen dengan Provider
function App() {
  const [tema, setTema] = useState("gelap");
  return (
    <TemaContext.Provider value={{ tema, setTema }}>
      <Halaman />
    </TemaContext.Provider>
  );
}

// 3. Ambil nilainya di komponen mana pun di dalam Provider
function Tombol() {
  const { tema, setTema } = useContext(TemaContext);
  return (
    <button
      className={tema}
      onClick={() => setTema(tema === "gelap" ? "terang" : "gelap")}
    >
      Ganti tema
    </button>
  );
}

Beberapa hal yang perlu kamu tahu:

  • useContext mencari Provider terdekat di atasnya. Kalau tidak ada Provider sama sekali, ia mengembalikan nilai default dari createContext.
  • Di React 19 kamu juga boleh menulis <TemaContext value={...}> langsung sebagai Provider. Bentuk .Provider masih berfungsi, jadi kamu akan sering melihat keduanya.
  • Saat value berubah, semua komponen yang memakai context itu dirender ulang. Karena itu context cocok untuk data global yang jarang berubah: tema, bahasa, user yang login. Untuk state kompleks yang sering berubah, pertimbangkan pola lain (misalnya useReducer atau library state management).
  • Context bukan pengganti props. Kalau data hanya turun satu atau dua level, oper lewat props saja, lebih jelas.

Rangkuman

  • Efek samping (fetch, timer, listener, judul dokumen) ditaruh di useEffect, yang jalan setelah render.
  • Tanpa array: setiap render. []: sekali saat mount. [a, b]: saat mount dan setiap a atau b berubah.
  • Kembalikan fungsi cleanup untuk menghentikan timer atau melepas listener. React memanggilnya sebelum efek berikutnya dan saat unmount.
  • Hindari infinite loop, masukkan semua nilai yang dipakai ke dependency, dan ingat StrictMode sengaja menjalankan efek dua kali di development.
  • createContext + Provider + useContext membagikan data ke banyak komponen tanpa prop drilling.

Selesaikan bab ini

  1. 1. Baca materi
  2. 2. Latihan kode (1)
  3. 3. Kuis (4 soal)

Latihan kode: Kapan efek jalan ulang, dan judul tab

Setelah setiap render, React memutuskan apakah efek dijalankan dengan membandingkan dependency array lama dan baru. Tiru logika itu sebagai fungsi murni.

  1. perluJalan(depsLama, depsBaru) mengembalikan true bila efek harus dijalankan:
    • depsBaru adalah undefined (tidak ada array): selalu true.
    • depsLama adalah null (render pertama): true.
    • Selain itu true bila ada satu posisi yang berbeda menurut Object.is, dan false bila semuanya sama.
  2. judulDokumen(belumDibaca) membuat teks untuk document.title di efek useEffect(() => { document.title = judulDokumen(n); }, [n]): 0 menghasilkan "Kotak Masuk", angka lain menghasilkan misalnya "(3) Kotak Masuk", dan lebih dari 99 menjadi "(99+) Kotak Masuk".

Cek pemahaman

  1. 1Kapan efek useEffect(fn, []) dijalankan?

  2. 2Di mode development dengan StrictMode, React sengaja menjalankan efek, cleanup, lalu efek lagi saat komponen pertama tampil.

  3. 3Kode efek mana yang menyebabkan infinite loop?

  4. 4Apa teks yang tampil, berurutan dari atas ke bawah?

    const Warna = createContext("merah");
    
    function Label() {
      const w = useContext(Warna);
      return <span>{w} </span>;
    }
    
    function App() {
      return (
        <>
          <Label />
          <Warna.Provider value="biru">
            <Label />
            <Warna.Provider value="hijau">
              <Label />
            </Warna.Provider>
          </Warna.Provider>
        </>
      );
    }