Memuat halaman…
Memuat halaman…
Pelajari React Hooks melalui alur bertahap: state, effect, context, custom hook, pola fetch data, dan latihan kecil yang dekat dengan project nyata.
Feedback kamu membantu kami memperbaiki materi berikutnya.
Silakan masuk terlebih dahulu untuk ikut berdiskusi
Login SekarangReact Hooks membantu kita menulis komponen React berbasis fungsi yang tetap bisa memiliki state, membaca context, menjalankan side effect, dan membungkus logika agar dapat digunakan ulang. Bagi pemula, hooks sering terasa membingungkan karena bentuknya sederhana, tetapi perilakunya sangat terkait dengan cara React melakukan render ulang.
Tutorial ini membahas hooks dengan pendekatan bertahap. Kita tidak hanya melihat sintaks, tetapi juga alasan kapan sebuah hook dipakai, apa konsekuensinya terhadap render, dan kesalahan umum yang perlu dihindari saat membangun aplikasi.
Setelah mengikuti tutorial ini, kamu diharapkan mampu:
useState untuk menyimpan data yang memengaruhi tampilan.useEffect untuk menjalankan side effect dengan dependency yang tepat.useContext untuk membaca data global sederhana.Kamu tidak harus mahir React, tetapi sebaiknya sudah paham dasar berikut:
| Prasyarat | Mengapa penting |
|---|---|
| JavaScript function | Komponen React modern biasanya ditulis sebagai function. |
| Array dan object | State sering berbentuk array atau object. |
| Callback | Event handler seperti onClick menerima function. |
| Promise dasar | Banyak side effect berkaitan dengan fetch data. |
Jika beberapa bagian belum kuat, tetap lanjutkan. Fokus utama tutorial ini adalah pola berpikir hooks, bukan menghafal seluruh API React.
Ketika React menjalankan komponen, React menghasilkan sebuah snapshot UI berdasarkan props dan state pada saat itu. Jika state berubah, React menjalankan ulang komponen untuk menghasilkan snapshot baru.
Perhatikan contoh sederhana berikut.
message bukan state. Nilainya dihitung setiap render. Jika name berubah, komponen dirender ulang dan message dihitung ulang. State baru dibutuhkan ketika sebuah nilai perlu bertahan antar-render dan perubahannya harus membuat UI diperbarui.
useState adalah hook paling dasar. Ia mengembalikan dua hal: nilai saat ini dan function untuk meminta React memperbarui nilai tersebut.
Ada tiga hal penting dari contoh ini. Pertama, count adalah nilai untuk render saat ini. Kedua, setCount tidak mengubah variabel count secara langsung. Ketiga, setelah setCount dipanggil, React menjadwalkan render baru dengan nilai terbaru.
Jika nilai baru bergantung pada nilai lama, gunakan bentuk callback. Ini menghindari bug saat beberapa update terjadi berdekatan.
Callback menerima nilai state paling mutakhir yang diketahui React. Pola ini cocok untuk counter, toggle, antrean, keranjang belanja, dan data yang diperbarui beberapa kali dari interaksi yang sama.
React mendeteksi perubahan berdasarkan referensi. Karena itu, jangan mengubah object atau array lama secara langsung. Buat salinan baru.
Untuk array, gunakan map, filter, atau spread, bukan push langsung pada state lama.
Render sebaiknya tetap murni: menerima input, menghasilkan UI. Aktivitas seperti fetch data, membaca storage, memasang event listener, atau mengubah title dokumen termasuk side effect. Untuk kebutuhan seperti itu, gunakan useEffect.
Dependency array [userId] berarti effect berjalan saat komponen pertama muncul dan setiap kali userId berubah. Cleanup function digunakan agar hasil request lama tidak menimpa state ketika komponen sudah berganti konteks.
Dependency array sering menjadi sumber bug. Aturan praktisnya: semua nilai dari luar effect yang dipakai di dalam effect perlu masuk dependency.
| Bentuk | Kapan berjalan | Catatan |
|---|---|---|
| Tanpa array | Setelah setiap render | Jarang dibutuhkan karena mudah membuat loop. |
[] | Setelah mount | Cocok untuk setup yang tidak bergantung props/state. |
[value] | Setelah mount dan saat value berubah | Ini pola paling umum. |
Contoh bug umum:
Kode di atas hanya mencari berdasarkan query awal. Jika query berubah, effect tidak berjalan lagi. Dependency yang benar adalah [query].
Context berguna ketika beberapa komponen perlu membaca data yang sama tanpa mengoper props terlalu jauh. Contoh ringan adalah theme, user session, atau preferensi bahasa.
Context bukan pengganti semua state management. Jika setiap perubahan context membuat banyak komponen render ulang, pertimbangkan memecah context atau memakai state management yang lebih sesuai.
Custom hook adalah function yang namanya diawali use dan boleh memanggil hooks lain. Tujuannya bukan membuat kode terlihat keren, melainkan memindahkan pola yang berulang ke tempat yang lebih mudah diuji dan dibaca.
Custom hook membuat komponen fokus pada UI, sedangkan detail sinkronisasi dengan storage berada di satu tempat.
Ada dua aturan inti:
Jangan menaruh hooks di dalam kondisi, loop, atau function biasa.
React mengandalkan urutan pemanggilan hooks yang sama pada setiap render. Jika hooks dipanggil secara kondisional, urutan itu berubah dan React tidak bisa memasangkan state dengan benar.
| Kesalahan | Dampak | Perbaikan |
|---|---|---|
| Mutasi state langsung | UI tidak selalu update | Buat object atau array baru. |
| Dependency effect kosong padahal memakai props | Data tidak sinkron | Masukkan dependency yang dipakai. |
| Menaruh fetch langsung di render | Request berulang tiap render | Pindahkan ke effect atau framework data layer. |
| Terlalu cepat membuat custom hook | Abstraksi sulit dipahami | Ekstrak setelah pola berulang terlihat. |
Buat komponen ReadingTracker dengan fitur berikut:
localStorage menggunakan custom hook.Contoh data awal:
Mulai dari useState, lalu baru pindahkan logika storage ke custom hook setelah komponen berjalan.
Kuis
Kapan nilai sebaiknya disimpan sebagai state?
Kuis
Apa tujuan dependency array pada useEffect?
React Hooks bukan sekadar API baru untuk mengganti class component. Hooks adalah cara menyusun logika UI berdasarkan render, state, dan side effect. Mulailah dari mental model bahwa render adalah snapshot. Gunakan useState untuk data yang memengaruhi UI, useEffect untuk side effect, useContext untuk data lintas komponen yang sederhana, dan custom hook saat ada pola logika yang benar-benar berulang.
function Greeting({ name }) {
const message = `Halo, ${name}`;
return <p>{message}</p>;
}import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<section>
<p>Jumlah klik: {count}</p>
<button onClick={() => setCount(count + 1)}>
Tambah
</button>
</section>
);
}function CounterActions() {
const [count, setCount] = useState(0);
function incrementThreeTimes() {
setCount((current) => current + 1);
setCount((current) => current + 1);
setCount((current) => current + 1);
}
return (
<button onClick={incrementThreeTimes}>
Tambah 3 dari {count}
</button>
);
}function ProfileForm() {
const [profile, setProfile] = useState({
name: "",
role: "student",
});
function updateName(event) {
setProfile((current) => ({
...current,
name: event.target.value,
}));
}
return (
<label>
Nama
<input value={profile.name} onChange={updateName} />
</label>
);
}function TodoList() {
const [todos, setTodos] = useState([]);
function addTodo(title) {
setTodos((current) => [
...current,
{ id: crypto.randomUUID(), title, done: false },
]);
}
function toggleTodo(id) {
setTodos((current) =>
current.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}
return null;
}import { useEffect, useState } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [status, setStatus] = useState("idle");
useEffect(() => {
let cancelled = false;
async function loadUser() {
setStatus("loading");
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
setStatus("success");
}
}
loadUser().catch(() => {
if (!cancelled) setStatus("error");
});
return () => {
cancelled = true;
};
}, [userId]);
if (status === "loading") return <p>Memuat data...</p>;
if (status === "error") return <p>Gagal memuat data.</p>;
return <pre>{JSON.stringify(user, null, 2)}</pre>;
}function BadSearch({ query }) {
const [result, setResult] = useState(null);
useEffect(() => {
fetch(`/api/search?q=${query}`)
.then((response) => response.json())
.then(setResult);
}, []);
return <pre>{JSON.stringify(result)}</pre>;
}import { createContext, useContext } from "react";
const ThemeContext = createContext("light");
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button className={theme === "dark" ? "button-dark" : "button-light"}>
Simpan
</button>
);
}
export function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}import { useEffect, useState } from "react";
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
if (typeof window === "undefined") return initialValue;
const stored = window.localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
function NameForm() {
const [name, setName] = useLocalStorage("display-name", "");
return (
<input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Nama tampilan"
/>
);
}function BadComponent({ enabled }) {
if (enabled) {
const [value, setValue] = useState("");
}
return null;
}const lessons = [
{ id: "state", title: "State dan Props" },
{ id: "effect", title: "Side Effect" },
{ id: "context", title: "Context" },
];