Untuk memakai useEffect di Next.js App Router, tandai file dengan ‘use client’ di baris pertama, lalu panggil fetch atau event browser di dalam useEffect. Hook ini tidak jalan di Server Component karena hook hanya hidup setelah React terhidrasi di browser. Langkah yang paling sering terlewat adalah cleanup dengan AbortController atau removeEventListener agar request dan listener tidak bocor saat pindah halaman.
Aturan Wajib useEffect Hanya Jalan di Client Component
Next.js merender Server Component di server tanpa DOM dan tanpa lifecycle browser. useEffect butuh browser, jadi file yang memakainya wajib Client Component.
- Tambahkan ‘use client’ sebagai baris paling atas sebelum import.
- Import dari React: import { useEffect, useState } from ‘react’.
- Jangan akses database, headers, cookies server, atau secret langsung dari effect ini.
'use client';
import { useEffect, useState } from 'react';
export default function StatusOnline() {
const [online, setOnline] = useState(true);
useEffect(() => {
const update = () => setOnline(navigator.onLine);
window.addEventListener('online', update);
window.addEventListener('offline', update);
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
};
}, []);
return <p>{online ? 'Online' : 'Offline'}</p>;
}
Langkah Fetch Data dan Cleanup dengan useEffect
Gunakan pola ini untuk data yang memang harus diambil dari browser, misalnya setelah interaksi user atau akses localStorage.
- Tandai file dengan ‘use client’.
- Atur state loading, data, dan error secara eksplisit.
<li Panggil fetch di dalam useEffect, bukan di body komponen.
'use client';
import { useEffect, useState } from 'react';
export default function DaftarUser() {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const res = await fetch('/api/users', { signal: controller.signal });
if (!res.ok) throw new Error('Gagal memuat data');
setData(await res.json());
} catch (err) {
if (err.name !== 'AbortError') setError(err.message);
}
}
load();
return () => controller.abort();
}, []);
if (error) return <p>{error}</p>;
return <ul>{data.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
- Array kosong []: jalan sekali setelah mount, cocok untuk listener dan fetch awal di client.
- Array terisi [query]: jalan ulang saat nilai berubah, cocok untuk pencarian.
- Tanpa array: jalan setiap render, hampir selalu menyebabkan fetch berulang.
Pola Aman vs Pola Keliru
| Kasus | Penyebab | Pola Benar |
|---|---|---|
| Effect jalan berulang | Tanpa dependency array | Tambahkan [] atau [query] |
| Fungsi async langsung di effect | Return promise merusak cleanup | Buat fungsi async di dalam effect |
| Listener menumpuk | Tidak ada cleanup | Kembalikan removeEventListener |
| Fetch awal lambat | Semua data diambil dari client | Pakai Async Server Component |
Untuk data awal halaman, biarkan Server Component mengambilnya lalu teruskan sebagai props. Simpan useEffect untuk window, document, media query, IntersectionObserver, dan event browser lain.
FAQ
Apakah useEffect bisa dipakai di Server Component Next Js?
Tidak. Pindahkan logika effect ke Client Component yang diimpor dari Server Component.
Kenapa useEffect jalan dua kali di development?
Itu Strict Mode React untuk menguji cleanup, jadi pastikan return cleanup menutup request dan listener.
Kapan tidak perlu useEffect?
Untuk fetch awal halaman, gunakan Async Server Component agar data tersedia sebelum HTML dikirim.