Komponen yang hanya menampilkan props tidak cukup untuk aplikasi nyata — kamu perlu UI yang bisa berubah berdasarkan interaksi pengguna. Di sinilah state berperan. State adalah data yang “dimiliki” oleh komponen dan menyebabkan komponen re-render saat nilainya berubah. Hooks adalah fungsi khusus React yang memberi komponen fungsi akses ke fitur-fitur React — useState dan useEffect adalah yang paling penting.
useState — Mengelola State Sederhana
import { useState } from 'react';
function Penghitung() {
// useState mengembalikan ilaiSaat ini, fungsiUpdate]
const itung, setHitung] = useState(0); // 0 = nilai awal
return (
<div>
<p>Hitungan: {hitung}</p>
<button onClick={() => setHitung(hitung + 1)}>+ Tambah</button>
<button onClick={() => setHitung(hitung - 1)}>- Kurang</button>
<button onClick={() => setHitung(0)}>Reset</button>
</div>
);
}
Aturan penting: Jangan pernah mengubah state secara langsung (
hitung = 5). Selalu gunakan fungsi setter (setHitung(5)). Hanya dengan setter React tahu perlu melakukan re-render.
State dan Re-render
function StatusLogin() {
const sLogin, setIsLogin] = useState(false);
const ama, setNama] = useState("Tamu");
const handleLogin = () => {
setIsLogin(true);
setNama("Ali Akbar");
};
const handleLogout = () => {
setIsLogin(false);
setNama("Tamu");
};
return (
<div>
<p>Status: {isLogin ? "✅ Login" : "❌ Belum login"}</p>
<p>Pengguna: {nama}</p>
{isLogin
? <button onClick={handleLogout}>Logout</button>
: <button onClick={handleLogin}>Login</button>
}
</div>
);
}
Immutability — Cara Update Objek dan Array
Saat state berupa objek atau array, kamu tidak boleh mengubahnya langsung:
function FormPengguna() {
const engguna, setPengguna] = useState({
nama: "",
email: "",
kota: ""
});
const handleUbah = (field, nilai) => {
// ❌ SALAH — jangan mutasi state langsung
// pengguna.nama = nilai;
// setPengguna(pengguna);
// ✅ BENAR — buat objek baru dengan spread operator
setPengguna({ ...pengguna, ield]: nilai });
};
return (
<form>
<input
value={pengguna.nama}
onChange={e => handleUbah("nama", e.target.value)}
placeholder="Nama"
/>
<input
value={pengguna.email}
onChange={e => handleUbah("email", e.target.value)}
placeholder="Email"
/>
<p>Data: {JSON.stringify(pengguna)}</p>
</form>
);
}
Update Array dalam State
function DaftarTugas() {
const ugas, setTugas] = useState([
{ id: 1, teks: "Belajar React", selesai: false },
{ id: 2, teks: "Membuat project", selesai: false },
]);
const nputBaru, setInputBaru] = useState("");
// Tambah elemen — spread + elemen baru
const tambahTugas = () => {
if (!inputBaru.trim()) return;
setTugas([...tugas, { id: Date.now(), teks: inputBaru, selesai: false }]);
setInputBaru("");
};
// Toggle selesai — map menghasilkan array baru
const toggleSelesai = (id) => {
setTugas(tugas.map(t =>
t.id === id ? { ...t, selesai: !t.selesai } : t
));
};
// Hapus — filter menghasilkan array baru
const hapus = (id) => {
setTugas(tugas.filter(t => t.id !== id));
};
return (
<div>
<div>
<input
value={inputBaru}
onChange={e => setInputBaru(e.target.value)}
onKeyDown={e => e.key === "Enter" && tambahTugas()}
placeholder="Tugas baru..."
/>
<button onClick={tambahTugas}>Tambah</button>
</div>
<ul>
{tugas.map(t => (
<li key={t.id} style={{ textDecoration: t.selesai ? "line-through" : "none" }}>
<input
type="checkbox"
checked={t.selesai}
onChange={() => toggleSelesai(t.id)}
/>
{t.teks}
<button onClick={() => hapus(t.id)}>🗑</button>
</li>
))}
</ul>
<p>{tugas.filter(t => t.selesai).length}/{tugas.length} selesai</p>
</div>
);
}
useEffect — Side Effects
useEffect digunakan untuk operasi di luar render: fetch data, subscribe event, update title, set timer:
import { useState, useEffect } from 'react';
function JudulDinamis({ halaman }) {
useEffect(() => {
// Dijalankan setelah setiap render (atau saat dependency berubah)
document.title = `${halaman} — KamusNgoding`;
// Cleanup function — dijalankan sebelum effect berikutnya atau saat unmount
return () => {
document.title = "KamusNgoding";
};
}, alaman]); // Dependency array: effect hanya jalan saat 'halaman' berubah
return <h1>{halaman}</h1>;
}
Fetch Data dengan useEffect
function DaftarArtikel() {
const rtikel, setArtikel] = useState([]);
const oading, setLoading] = useState(true);
const rror, setError] = useState(null);
useEffect(() => {
// Jalankan sekali saat komponen pertama mount ([] = array kosong)
const ambilData = async () => {
try {
setLoading(true);
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
if (!response.ok) throw new Error("Gagal mengambil data");
const data = await response.json();
setArtikel(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
ambilData();
}, []); // [] = hanya jalan sekali saat mount
if (loading) return <p>Memuat data...</p>;
if (error) return <p style={{ color: "red" }}>Error: {error}</p>;
return (
<ul>
{artikel.map(a => (
<li key={a.id}>{a.title}</li>
))}
</ul>
);
}
Tiga Bentuk Dependency Array
// 1. Tanpa dependency array — jalan setiap render
useEffect(() => {
console.log("Render terjadi!");
});
// 2. Array kosong [] — jalan sekali saat mount
useEffect(() => {
console.log("Komponen di-mount!");
return () => console.log("Komponen di-unmount!");
}, []);
// 3. Dengan dependencies — jalan saat nilai berubah
useEffect(() => {
console.log(`Pencarian: ${query}`);
// Lakukan pencarian...
}, uery]); // Jalan saat 'query' berubah
useRef — Referensi yang Tidak Menyebabkan Re-render
import { useState, useRef } from 'react';
function Stopwatch() {
const aktu, setWaktu] = useState(0);
const erjalan, setBerjalan] = useState(false);
const intervalRef = useRef(null); // Menyimpan ID interval tanpa trigger re-render
const mulai = () => {
if (berjalan) return;
setBerjalan(true);
intervalRef.current = setInterval(() => {
setWaktu(w => w + 1); // Functional update untuk state yang bergantung nilai sebelumnya
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
setBerjalan(false);
};
const reset = () => {
stop();
setWaktu(0);
};
return (
<div>
<p style={{ fontSize: "2rem" }}>{waktu} detik</p>
<button onClick={mulai} disabled={berjalan}>▶ Mulai</button>
<button onClick={stop} disabled={!berjalan}>⏸ Stop</button>
<button onClick={reset}>↺ Reset</button>
</div>
);
}
Rules of Hooks
// ✅ BENAR — panggil hooks di level atas komponen
function KomponenBenar() {
const ilai, setNilai] = useState(0);
useEffect(() => { /* ... */ }, ilai]);
return <div>{nilai}</div>;
}
// ❌ SALAH — jangan panggil hooks dalam kondisi
function KomponenSalah({ aktif }) {
if (aktif) {
const ilai, setNilai] = useState(0); // ❌ Hooks tidak boleh dalam kondisi!
}
return <div />;
}
Troubleshooting: Error yang Sering Muncul
useEffect berjalan terus-menerus — infinite loop
Penyebab: Dependency array berisi objek atau array yang dibuat ulang setiap render, atau state yang diupdate di dalam effect tanpa kondisi henti.
Solusi:
// Infinite loop — `config` dibuat ulang tiap render
const config = { limit: 10 };
useEffect(() => {
fetchData(config);
}, [config]); // config selalu "baru" setiap render
// Pindahkan objek ke dalam effect agar tidak jadi dependency
useEffect(() => {
const config = { limit: 10 };
fetchData(config);
}, []); // hanya jalan sekali saat mount
// Untuk state: pastikan ada kondisi henti sebelum update
useEffect(() => {
if (!dataSudahDimuat) {
fetchData().then(setData);
}
}, [dataSudahDimuat]);
Warning: Can't perform a React state update on an unmounted component
Penyebab: Komponen di-unmount sebelum operasi async (fetch, setTimeout) selesai, lalu mencoba mengupdate state yang sudah tidak ada.
Solusi:
useEffect(() => {
let isMounted = true;
fetch('/api/data')
.then(res => res.json())
.then(json => {
if (isMounted) setData(json); // hanya update jika masih mounted
});
return () => {
isMounted = false; // cleanup saat komponen di-unmount
};
}, []);
React Hook useEffect is called conditionally — Error ESLint
Penyebab: Memanggil hook di dalam kondisi, loop, atau fungsi callback — melanggar Rules of Hooks yang mengharuskan hook selalu dipanggil di urutan yang sama.
Solusi:
// Salah — hook di dalam kondisi
function Komponen({ isLoggedIn }) {
if (isLoggedIn) {
const [data, setData] = useState(null); // Error!
}
}
// Benar — selalu di top level, kondisi di dalam hook
function Komponen({ isLoggedIn }) {
const [data, setData] = useState(null);
useEffect(() => {
if (isLoggedIn) fetchData().then(setData);
}, [isLoggedIn]);
}
Pertanyaan yang Sering Diajukan
Mengapa tidak boleh mengubah state langsung?
React menggunakan referensi objek untuk mendeteksi perubahan. Jika kamu memutasi objek/array yang sudah ada (arr.push(x)), referensinya tidak berubah — React tidak mendeteksi perubahan dan tidak melakukan re-render. Dengan membuat objek/array baru ([...arr, x]), referensi berubah dan React tahu perlu render ulang.
Apa perbedaan setNilai(n + 1) dan setNilai(n => n + 1)?
setNilai(n + 1) menggunakan nilai n saat fungsi didefinisikan — bisa stale (kadaluarsa) jika ada banyak update berurutan. setNilai(n => n + 1) menggunakan nilai terbaru sebagai argumen — selalu akurat. Gunakan bentuk fungsi saat update bergantung pada nilai state sebelumnya, terutama dalam interval atau event handler async.
Kapan useEffect perlu cleanup function?
Kapan pun useEffect membuat sesuatu yang perlu “dibersihkan”: (1) setInterval/setTimeout — harus di-clearInterval/clearTimeout, (2) event listener — harus di-removeEventListener, (3) WebSocket/subscription — harus disconnect/unsubscribe, (4) request API — bisa di-abort. Tanpa cleanup, bisa terjadi memory leak atau bug saat komponen di-unmount.
Apa itu “stale closure” dalam hooks?
Stale closure terjadi saat sebuah fungsi “mengingat” nilai lama dari state karena dibuat sebelum state diperbarui. Solusinya: gunakan functional update setState(prev => ...) untuk state, atau tambahkan state ke dependency array useEffect. Eslint plugin react-hooks/exhaustive-deps membantu mendeteksi dependency yang hilang.
Kesimpulan
| Hook | Kegunaan | Contoh |
|---|---|---|
useState | Menyimpan state lokal | const , setN] = useState(0) |
useEffect | Side effects & lifecycle | useEffect(() => {...}, eps]) |
useRef | Referensi tanpa re-render | ref.current = value |
Artikel sebelumnya: Komponen dan Props di React — membangun UI reusable.
Langkah selanjutnya: Event Handling dan Form di React — cara menangani interaksi pengguna dan membuat form yang lengkap.