Bab 16

React: Efek & Pengambilan Data

Bab 14 dan 15 merender tampilan dari state yang lahir di dalam halaman, ketikan pengguna dan tombol yang ditekan. Bab ini menyambungkannya dengan dunia luar: data yang harus diminta dari server, seperti cuaca dari bab 11. Pertanyaannya dua. Permintaan itu ditulis di mana dalam komponen, dan apa yang terjadi di baliknya selama data masih di jalan.

Kuka berdiri di pintu kamar yang barusan ditata sambil menyerahkan secarik catatan belanja ke kurir yang membawa tas besar, rak di dalam kamar masih ada yang kosong menunggu barang datang.
Kurir berangkat setelah kamar tertata, bukan di tengah penataan. Efek jalan setelah render.

Mengapa efek ada

Fungsi komponen punya satu tugas: mengembalikan tampilan dari props dan state. React menjalankannya berkali-kali, setiap ada re-render, dan diharapkan cepat serta tidak meninggalkan jejak. Bandingkan dengan fetch dari bab 11. Permintaan itu menyentuh dunia luar, butuh waktu, dan bisa gagal. Pekerjaan yang keluar dari hitungan tampilan seperti ini disebut efek samping (side effect), contohnya meminta data lewat jaringan, menulis localStorage, atau mengubah judul tab.

Kalau fetch ditulis langsung di badan komponen, setiap re-render memicu permintaan baru. Komponen penghitung bab 14 yang ditekan sepuluh kali akan memesan cuaca sepuluh kali. React menyediakan tempat resminya: useEffect, hook kedua setelah useState. Hook adalah fungsi resmi React untuk menambah kemampuan pada komponen, dan useEffect menampung pekerjaan dunia luar lalu menjalankannya di waktu yang tepat, yaitu setelah render selesai.

Data datang terlambat

Render pertama tidak pernah menunggu data. fetch butuh waktu, dan membekukan render berarti membekukan halaman, kegagalan yang sudah dibedah di bab 11. Maka komponen yang mengambil data selalu melewati tiga keadaan. Pertama memuat (loading), data belum tiba. Lalu berhasil atau gagal, begitu jawaban datang.

Ketiganya cukup ditampung sebagai state biasa dari bab 14. Yang berubah hanya sumber nilainya: dulu ketikan pengguna, kini jawaban server. Kondisi dari bab 12 memilih tampilan mana yang dirender untuk tiap keadaan, teks Memuat selama menunggu, data kalau berhasil, pesan gagal kalau permintaan putus.

useEffect dan array dependensi

Bentuk pemanggilannya dua bagian. Bagian pertama fungsi efek, berisi pekerjaan dunia luarnya. Bagian kedua array dependensi (dependency array), daftar nilai yang menentukan kapan efek perlu jalan lagi.

Cara bacanya tiga. Array kosong berarti jalan sekali, setelah render pertama, cocok untuk data yang tidak bergantung pada apa pun. Array berisi nilai, misalnya nama kota, berarti jalan setelah render pertama dan setiap kali kota berganti. Array yang lupa ditulis berarti efek jalan setiap selesai render, dan untuk fetch itu permintaan berulang tanpa henti. Kesalahan pemula yang paling sering justru ada di bagian kedua ini, array yang tertinggal satu pasang kurung siku.

Di balik efek

Urutan kerja React setiap ada perubahan: jalankan ulang fungsi komponen, perbarui DOM, baru jalankan efek yang array dependensinya berubah. Render dulu, efek belakangan. Itu sebabnya badan komponen bukan tempat fetch, badan fungsi jalan jauh sebelum DOM siap, dan berulang tiap re-render. Sebaliknya, re-render yang dipicu state lain tidak mengulang efek, selama dependensinya tidak berubah.

Fungsi efek boleh mengembalikan satu fungsi lagi, namanya cleanup, pekerjaan beres-beres. React menjalankannya tepat sebelum efek berikutnya, dan terakhir kali saat komponen dibongkar (unmount), keluar dari layar. Untuk fetch, isinya biasanya sesederhana satu bendera, tanda bahwa hasil permintaan ini tidak akan diterima lagi.

Balapan dua jawaban

Dua kurir pulang bersamaan membawa bon berbeda, Kuka berdiri di pintu hanya menerima bon dengan angka dua sambil menolak halus bon dengan angka satu yang sudah tertunda di tangannya.
Dua bon, dua kurir, hanya permintaan terbaru yang diterima.

Pengaman itu perlu karena jawaban tidak selalu berurutan. Kota berubah dari Jakarta ke Bandung, dua permintaan berada di jalan, dan yang dikirim belakangan bisa pulang duluan. Tanpa pengaman, suhu Bandung tampil dulu lalu tertimpa suhu Jakarta, dan layar menunjukkan kota yang salah. Peristiwa dua jawaban yang datang tak berurutan ini disebut balapan (race).

Penangkalnya memakai cleanup dari tadi. Setiap efek baru mematikan bendera efek sebelumnya, dan jawaban yang datang setelah benderanya mati dibuang. Biayanya tiga baris. Tanpanya bug muncul hanya sesekali, justru jenis yang paling susah dicari.

Analogi: kurir dan catatan belanja

Satu gambar untuk semuanya, lanjutan tukang dan denah dari bab 14. Kamar yang ditata dari denah adalah tampilan komponen. Sebagian perabot tidak ada di gudang, ia harus dipesan ke toko, dan kurir yang kamu kirim itulah efek. Aturannya satu: kurir berangkat setelah kamar tertata, bukan di tengah penataan, sama dengan efek yang jalan setelah render. Catatan belanja di tangan kurir adalah array dependensi. Catatan tidak berubah, kurir tidak dikirim ulang, sekalipun kamu menata ulang kamar. Satu baris catatan berganti, kurir berangkat lagi. Sebelum kurir baru melangkah, pesanan lama kamu tandai tidak lagi diterima, itulah cleanup. Dan selama barang masih di jalan, rak memang kosong, itulah layar memuat.

Pasangan contoh

Contohnya cuaca Jakarta dari API Open-Meteo, sumber yang sama dengan demo bab 11. Vanilanya satu fungsi async: tulis Memuat dengan textContent, fetch, cek jawaban.ok, kumpulkan jawaban dengan json(), tulis suhu, dan try/catch menampung gagal. Pemeriksaan jawaban.ok penting karena fetch hanya gagal otomatis saat jaringan gagal, bukan saat server menjawab 404 atau 500. Tombol Muat ulang memanggil fungsi yang sama. React-nya dua state, keadaan dan suhu, plus satu useEffect dengan array kosong. Fungsi komponen membaca keadaan lalu me-render tampilan yang cocok, dan useEffect hanya bertugas memasukkan hasil ke state begitu jawaban datang.

Kode React di kolom kanan ditulis dengan JSX dari bab 15, sedangkan demo di bawah memakai bentuk mentahnya, React.createElement, yang jalan tanpa build tool. Versi pasangan sengaja dibuat ramping tanpa pengaman balapan, pengamannya ada di demo.

Vanilla JavaScript

const urlCuaca = "https://api.open-meteo.com/v1/forecast?latitude=-6.2&longitude=106.8&current=temperature_2m";
const kotak = document.querySelector("#cuaca");

async function tampilkanCuaca() {
  kotak.textContent = "Memuat...";
  try {
    const jawaban = await fetch(urlCuaca);
    if (!jawaban.ok) throw new Error("HTTP " + jawaban.status);
    const data = await jawaban.json();
    kotak.textContent = "Suhu di Jakarta: "
      + data.current.temperature_2m + " °C";
  } catch {
    kotak.textContent = "Gagal memuat.";
  }
}

tampilkanCuaca();
document.querySelector("#muat")
  .addEventListener("click", tampilkanCuaca);

React

function Cuaca() {
  const [keadaan, setKeadaan] = React.useState("memuat");
  const [suhu, setSuhu] = React.useState(null);

  React.useEffect(function () {
    async function ambil() {
      try {
        const jawaban = await fetch(urlCuaca);
        if (!jawaban.ok) throw new Error("HTTP " + jawaban.status);
        const data = await jawaban.json();
        setSuhu(data.current.temperature_2m);
        setKeadaan("berhasil");
      } catch {
        setKeadaan("gagal");
      }
    }
    ambil();
  }, []);

  if (keadaan === "memuat") return <p>Memuat...</p>;
  if (keadaan === "gagal") return <p>Gagal memuat.</p>;
  return <p>Suhu di Jakarta: {suhu} °C</p>;
}

Perbedaan inti bab ini ada di sini. Di vanilla, kamu yang mengatur kapan fetch jalan dan menulis sendiri pembaruan teks untuk tiap keadaan, satu textContent per keadaan. Di React, keadaan adalah state, tampilan mengikutinya, dan useEffect hanya memasukkan hasil dunia luar ke state. Tiga keadaan, tiga tampilan, tanpa satu baris pun menyentuh DOM langsung. Dan polanya tetap yang sama sejak bab 14: React tidak menghapus pekerjaan vanilla, ia membakukan urutannya, render dulu, efek belakangan, data yang datang menyusul lewat state.

Demo Hidup

Demo memuat React sungguhan lewat CDN ESM dan mengambil cuaca yang sama dengan pasangan contoh, lengkap dengan pengaman balapan. Teks Memuat data cuaca tampil dulu, lalu berganti suhu Jakarta begitu jawaban tiba. Tombol Muat ulang mengganti isi state coba, dan karena coba tercatat di array dependensi, efek jalan lagi dari awal.

Demo ini menjalankan komponen React yang mem-fetch data nyata dari API Open-Meteo, tanpa build tool. Perhatikan tiga keadaannya berganti sendiri: memuat, berhasil, dan gagal kalau jaringan putus.

Memuat React dari CDN...

Begini cara baca demonya. useEffect membungkus pemanggilan fetch, dengan array dependensi berisi coba, dan coba hanya berubah saat tombol Muat ulang ditekan. Di dalam efek ada bendera bernama hidup yang nilainya true, dan fungsi cleanup mengembalikannya menjadi false sebelum efek berikutnya jalan. Jawaban yang datang setelah benderanya mati dibuang, itulah penangkal balapan dari tadi. Sisanya pekerjaan state biasa: setKeadaan dan setSuhu mengubah data, tampilan mengikuti, dan tidak ada satu baris pun yang menyentuh DOM secara langsung.

Ringkasan bab ini. Komponen menghitung tampilan, dan pekerjaan dunia luar seperti fetch tidak boleh ikut di dalamnya, tempatnya useEffect yang jalan setelah render. Array dependensi menentukan kapan efek berulang, fungsi cleanup membereskan efek sebelumnya, dan bendera di cleanup menangkal dua jawaban yang pulang tak berurutan. Tiga keadaan, memuat, berhasil, gagal, cukup ditampung sebagai state biasa. Bab berikutnya merapikan pola ini menjadi fungsi yang bisa dipakai ulang: custom hooks.

Rujukan