Reactjs Hooks Usestate Useeffect Usememo Usecallback Useref
Definisi
Hooks adalah API fungsi di React yang dipakai untuk memberi komponen fungsional kemampuan seperti menyimpan state, menjalankan side effect, menyimpan referensi nilai lintas render, dan mengoptimalkan nilai atau fungsi yang mahal untuk dihitung ulang.
Lima hooks dasar yang paling sering muncul di komponen aplikasi nyata adalah:
useStateuntuk state lokal yang memicu rerender.useEffectuntuk sinkronisasi dengan dunia luar setelah render.useMemountuk menyimpan hasil perhitungan agar tidak dihitung ulang setiap render.useCallbackuntuk menyimpan referensi fungsi agar stabil antar render.useRefuntuk menyimpan nilai mutable atau referensi DOM tanpa memicu rerender.
Kalau dipakai dengan tepat, hooks membuat komponen tetap kecil, terukur, dan mudah di-debug. Kalau dipakai asal, masalah yang muncul biasanya bukan error sintaks, melainkan rerender berulang, dependency array yang salah, atau efek yang jalan di waktu yang tidak diinginkan.
Gambaran besar alur render
Inti yang perlu diingat:
useStatemengubah data yang memicu render.useEffectbiasanya dijalankan sesudah render selesai tampil.useMemodanuseCallbacktidak menghentikan render; keduanya hanya membantu menjaga hasil atau referensi tetap stabil.useRefmenyimpan nilai yang bertahan antar render tanpa ikut menjadi pemicu render.
useState
Kapan dipakai
Pakai useState saat komponen perlu menyimpan data yang berubah dan perubahan itu harus terlihat di UI.
Contoh umum:
- nilai input form
- toggle buka/tutup panel
- daftar data hasil fetch lokal
- status loading atau error
- tab aktif, filter aktif, pagination lokal
Efek terhadap rerender
Saat setState dipanggil, React menjadwalkan rerender komponen tersebut dan subtree yang bergantung pada hasil render itu.
Artinya:
- nilai state berubah
- fungsi komponen dijalankan ulang
- JSX dihitung ulang
- UI diperbarui bila hasil render berubah
Contoh
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Klik: {count}
</button>
);
}Catatan penting
- State bersifat snapshot per render. Kalau memakai nilai lama di dalam handler, pastikan update-nya aman.
- Untuk update yang bergantung pada nilai sebelumnya, lebih aman pakai bentuk fungsi:
setCount(prev => prev + 1);Jebakan umum
- Menaruh state terlalu banyak. Kalau satu data bisa dihitung dari props atau state lain, jangan disalin ke state tanpa alasan.
- Mengubah object/array state secara mutatif. React butuh referensi baru agar perubahan terbaca.
// Hindari
user.name = 'Budi';
setUser(user);
// Lebih aman
setUser(prev => ({ ...prev, name: 'Budi' }));useEffect
Kapan dipakai
Pakai useEffect untuk side effect, yaitu pekerjaan yang menyentuh dunia luar dari proses render murni.
Contoh:
- fetch data
- subscribe ke event listener
- sinkronisasi title halaman
- menulis ke storage
- integrasi dengan library non-React
- cleanup subscription atau timer
Efek terhadap rerender
useEffect sendiri tidak otomatis memicu rerender. Yang memicu rerender adalah perubahan state yang dilakukan di dalam effect.
Urutan sederhananya:
- Komponen render.
- React commit hasil render ke DOM.
useEffectdijalankan.- Kalau effect memanggil
setState, render berikutnya terjadi.
Contoh
import { useEffect, useState } from 'react';
export default function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
let cancelled = false;
async function load() {
const res = await fetch('/api/users');
const data = await res.json();
if (!cancelled) {
setUsers(data);
}
}
load();
return () => {
cancelled = true;
};
}, []);
return <pre>{JSON.stringify(users, null, 2)}</pre>;
}Dependency array
Dependency array menentukan kapan effect dijalankan ulang.
[]→ hanya setelah mount[a, b]→ ulang kalauaataubberubah referensinya- tanpa dependency array → jalan setiap render
Jebakan umum
- Dependency kurang lengkap: effect memakai nilai
propsataustate, tetapi tidak dicantumkan. Akibatnya effect membaca nilai lama. - Dependency berlebihan: memasukkan object atau function baru di setiap render, sehingga effect terus jalan.
- Infinite loop: effect mengubah state yang justru menjadi dependency effect itu sendiri.
Contoh loop yang sering terjadi:
useEffect(() => {
setCount(count + 1);
}, [count]);Kalau memang perlu sinkronisasi seperti ini, pikirkan ulang desain state-nya. Sering kali yang dibutuhkan bukan effect, melainkan derived state atau event handler.
Catatan praktis
- Jangan taruh logika render murni di
useEffect. - Kalau efek hanya menghitung nilai dari props/state tanpa side effect, lebih cocok
useMemoatau perhitungan biasa. - Cleanup penting untuk interval, subscription, listener, dan request yang bisa dibatalkan.
useMemo
Kapan dipakai
Pakai useMemo saat ada perhitungan yang mahal atau saat perlu menjaga referensi hasil tetap stabil antar render.
Contoh umum:
- filter/sort daftar besar
- transform data kompleks
- membangun object props untuk child yang sensitif terhadap referensi
- menghindari kerja ulang yang tidak perlu pada render sering
Efek terhadap rerender
useMemo tidak mencegah rerender komponen. Komponen tetap rerender kalau state atau props berubah.
Yang dilakukan useMemo adalah:
- menghitung ulang nilai hanya jika dependency berubah
- mengembalikan hasil lama kalau dependency sama
Contoh
import { useMemo } from 'react';
export default function ProductList({ products, query }) {
const filtered = useMemo(() => {
return products.filter(product =>
product.name.toLowerCase().includes(query.toLowerCase())
);
}, [products, query]);
return <pre>{JSON.stringify(filtered, null, 2)}</pre>;
}Jebakan umum
- Memakai
useMemountuk semua hal. Kalau perhitungannya murah, memoization justru menambah kompleksitas. - Mengira
useMemomenjaga semua child dari rerender. Itu hanya benar kalau child juga memang bergantung pada referensi hasil memo itu. - Dependency array salah, sehingga hasil lama dipakai padahal input sudah berubah.
Catatan praktis
Gunakan useMemo sebagai optimisasi yang terukur, bukan kebiasaan default. Kalau belum ada bukti bottleneck, sederhanakan dulu.
useCallback
Kapan dipakai
Pakai useCallback saat perlu menjaga referensi fungsi tetap stabil, biasanya ketika fungsi itu diteruskan sebagai props ke child yang dioptimalkan dengan memo, atau dipakai sebagai dependency hook lain.
Contoh umum:
- handler yang dikirim ke child memoized
- function dependency di effect atau memo lain
- callback untuk event subscription yang butuh referensi stabil
Efek terhadap rerender
useCallback tidak menghentikan komponen induk dari rerender.
Fungsinya adalah:
- mencegah fungsi baru dibuat setiap render bila dependency tidak berubah
- membantu child
memotidak ikut rerender hanya karena referensi function berganti
Contoh
import { memo, useCallback, useState } from 'react';
const Button = memo(function Button({ onClick }) {
return <button onClick={onClick}>Tambah</button>;
});
export default function Counter() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<>
<p>{count}</p>
<Button onClick={handleClick} />
</>
);
}Jebakan umum
- Memakai
useCallbacktanpa kebutuhan nyata. Kalau tidak ada child memoized atau dependency lain yang sensitif pada referensi, manfaatnya kecil. - Dependency array yang tidak lengkap membuat callback menangkap nilai lama.
- Mengandalkan
useCallbackuntuk “menghemat semua rerender”. Itu bukan fungsinya.
Hubungan dengan useMemo
Secara konsep:
useMemomenyimpan hasil nilaiuseCallbackmenyimpan hasil fungsi
Bisa dianggap useCallback(fn, deps) mirip useMemo(() => fn, deps) secara bentuk, tetapi gunakan sesuai niatnya agar kode tetap terbaca.
useRef
Kapan dipakai
Pakai useRef saat perlu menyimpan sesuatu yang bertahan antar render, tetapi perubahan nilainya tidak perlu memicu rerender.
Contoh umum:
- referensi DOM
- nilai mutable seperti timer ID
- flag internal
- menyimpan nilai sebelumnya
- cache kecil yang tidak perlu tampil ke UI
Efek terhadap rerender
Perubahan ref.current tidak memicu rerender.
Itu bedanya yang paling penting dengan useState.
Contoh DOM ref
import { useEffect, useRef } from 'react';
export default function SearchInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} placeholder="Cari..." />;
}Contoh menyimpan nilai sebelumnya
import { useEffect, useRef } from 'react';
export default function PreviousValue({ value }) {
const prevRef = useRef(value);
useEffect(() => {
prevRef.current = value;
}, [value]);
return <p>Sebelumnya: {prevRef.current}</p>;
}Jebakan umum
- Menaruh data UI utama di ref. Kalau nilainya harus tampil di layar, pakai state, bukan ref.
- Menganggap ref adalah state versi ringan. Bukan. Ref tidak memberi reactivity.
- Membaca ref untuk logika yang seharusnya memicu render. Hasilnya bisa terasa “tidak sinkron” dengan UI.
Perbandingan cepat
| Hook | Dipakai untuk | Memicu rerender? | Catatan utama |
|---|---|---|---|
useState | data UI yang berubah | Ya, saat setter dipanggil | pilihan default untuk state lokal |
useEffect | side effect setelah render | Tidak langsung | hati-hati dependency array dan cleanup |
useMemo | hasil perhitungan / referensi bernilai stabil | Tidak | optimisasi, bukan alat utama |
useCallback | referensi fungsi stabil | Tidak | berguna saat passing callback ke child memoized |
useRef | nilai mutable / DOM ref | Tidak | update current tidak rerender |
Dependency array yang salah: pola yang sering bikin bug
Bagian ini penting karena banyak bug React bukan berasal dari hook yang salah, melainkan dependency array yang tidak sesuai.
1. Dependency kurang
useEffect(() => {
console.log(user.name);
}, []);Kalau user.name bisa berubah, effect ini akan terus membaca nilai awal.
2. Dependency terlalu banyak
const options = { mode: 'dark' };
useEffect(() => {
connect(options);
}, [options]);Karena options dibuat ulang setiap render, effect ikut jalan terus.
Solusi yang lebih rapi:
const options = useMemo(() => ({ mode: 'dark' }), []);atau pindahkan object ke dalam effect kalau memang hanya dipakai di sana.
3. Function dependency yang berubah terus
const handleSave = () => {
saveDraft(form);
};
useEffect(() => {
register(handleSave);
}, [handleSave]);Karena handleSave baru setiap render, effect bisa terus aktif. Kalau perlu stabil, bungkus dengan useCallback.
4. Effect dipakai untuk turunan data yang sebenarnya bisa dihitung langsung
Kalau data hanya hasil transform dari props/state, jangan simpan hasilnya ke state lalu sinkronkan lewat effect kalau tidak perlu.
Lebih sederhana:
const filtered = useMemo(() => filterItems(items, query), [items, query]);Daripada:
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(filterItems(items, query));
}, [items, query]);Pendekatan pertama biasanya lebih bersih dan lebih sedikit tempat bug.
Kapan harus memilih hook yang mana
Gunakan pertanyaan cepat ini:
- Perlu data yang tampil di UI dan berubah akibat interaksi? →
useState - Perlu sinkronisasi dengan API luar React setelah render? →
useEffect - Perlu menghitung hasil mahal atau menjaga referensi nilai? →
useMemo - Perlu menjaga referensi fungsi tetap stabil? →
useCallback - Perlu menyimpan nilai mutable atau DOM tanpa rerender? →
useRef
Contoh kombinasi di komponen nyata
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
export default function SearchDashboard({ products }) {
const [query, setQuery] = useState('');
const inputRef = useRef(null);
const filteredProducts = useMemo(() => {
return products.filter(product =>
product.name.toLowerCase().includes(query.toLowerCase())
);
}, [products, query]);
const handleFocus = useCallback(() => {
inputRef.current?.focus();
}, []);
useEffect(() => {
document.title = `Hasil: ${filteredProducts.length}`;
}, [filteredProducts.length]);
return (
<section>
<input
ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Cari produk"
/>
<button onClick={handleFocus}>Fokus ke input</button>
<p>Total hasil: {filteredProducts.length}</p>
</section>
);
}Di contoh ini:
useStatemenyimpan query input.useRefmenyimpan referensi input.useMemomenghitung hasil filter.useCallbackmenjaga handler stabil.useEffectmenyinkronkan judul halaman.
Catatan implementasi
- Jangan pakai hooks di dalam kondisi, loop, atau fungsi biasa. Hooks harus dipanggil di level teratas komponen atau custom hook.
- Kalau dependency array terasa sulit dipenuhi, itu sering tanda desain state-nya perlu dirapikan.
- Optimisasi hanya masuk akal kalau ada data besar, rerender sering, atau child sensitif terhadap referensi.
- Banyak kasus bisa selesai dengan state yang lebih sederhana dan komponen yang lebih kecil, bukan dengan hook tambahan.
Kesimpulan
Untuk praktik sehari-hari, urutan berpikir yang aman biasanya begini:
- Pakai
useStateuntuk state yang benar-benar tampil di UI. - Pakai
useEffecthanya untuk side effect. - Pakai
useMemodanuseCallbackkalau ada alasan performa atau stabilitas referensi yang jelas. - Pakai
useRefuntuk nilai mutable dan akses DOM tanpa rerender.
Kalau satu komponen mulai terasa rumit, biasanya masalahnya bukan “kurang hook”, melainkan hook dipakai untuk tugas yang tidak tepat.