Fetch di Client Component
Kapan fetch di client dibenarkan: SWR, polling, dan interaksi.
Kapan fetch di client dibenarkan?
Bayangkan restoran lagi. Dapur (server) menyiapkan hidangan pembuka sebelum tamu datang, itu fetch di Server Component. Tapi saat tamu di tengah makan meminta saus tambahan atau memesan dessert, pelayan (client) yang mengambilkannya saat itu juga. Tidak masuk akal menyuruh tamu pulang dan datang lagi besok hanya untuk segelas es teh.
Itulah kenapa fetch di client tetap ada. Tidak semua data bisa disiapkan di server karena belum diketahui saat halaman di-render. Tiga kasus yang sah:
- Data yang berubah karena interaksi user, misalnya search-as-you-type atau filter yang digeser-geser.
- Polling atau update realtime, misalnya harga saham yang refresh tiap 5 detik.
- Data yang tidak boleh di-cache server, misalnya data sangat personal yang berbeda tiap detik.
Pola dasar: useEffect (boleh, tapi mentah)
// components/SearchBox.tsx
// Contoh 1: search-as-you-type dengan debounce manual
"use client";
import { useEffect, useState } from "react";
export default function SearchBox() {
const [q, setQ] = useState("");
const [results, setResults] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!q) {
setResults([]);
return;
}
setLoading(true);
const t = setTimeout(() => {
// debounce 300ms: jangan fetch tiap ketikan
fetch(`/api/search?q=${encodeURIComponent(q)}`)
.then((r) => r.json())
.then((d) => {
setResults(d);
setLoading(false);
});
}, 300);
return () => clearTimeout(t); // cleanup: batalkan timer lama
}, [q]);
return (
<>
<input value={q} onChange={(e) => setQ(e.target.value)} />
{loading ? <p>Mencari...</p> : <ResultList items={results} />}
</>
);
}Pola ini bekerja, tapi perhatikan berapa banyak yang ditulis manual: loading state, debounce, cleanup, error handling (yang bahkan belum ada di contoh). Setiap komponen fetch mengulang boilerplate yang sama.
Lebih baik: SWR
// components/Profile.tsx
// Contoh 2: SWR mengurus caching, deduping, dan revalidasi otomatis
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function Profile() {
const { data, error, isLoading } = useSWR("/api/me", fetcher);
if (isLoading) return <p>Memuat...</p>;
if (error) return <p>Gagal memuat profil.</p>;
return <p>Halo, {data.name}</p>;
}SWR (stale-while-revalidate) memberi tiga hal gratis: caching (data yang sama tidak di-fetch ulang), deduping (lima komponen meminta URL sama hanya memicu satu request), dan revalidasi otomatis (refetch saat tab kembali fokus). Semua itu harus ditulis manual kalau pakai useEffect polos.
Catatan teknis: Aturan praktisnya sederhana: fetch awal di server, fetch lanjutan yang dipicu interaksi di client. Jangan fetch di client hanya karena "lebih familiar", karena kamu membayar mahal: kehilangan SEO (crawler melihat halaman kosong) dan kecepatan (user menunggu JS + request berantai). Kalau datanya bisa disiapkan sebelum halaman dikirim, siapkan di server.
Tantangan
Search as-you-type
Buat komponen search client yang fetch ke API setiap user mengetik (dengan debounce 300ms). Tampilkan hasil sebagai daftar.