Menu

Aturan Hook di React: Mengapa Urutan Penting dan Solusinya

Hook punya dua aturan: panggil di tingkat teratas komponen, jangan pernah di dalam kondisi, perulangan, atau fungsi bersarang, dan panggil hanya dari function component atau custom hook. Pelajari mengapa React butuh urutan pemanggilan yang sama di setiap render dan cara memperbaiki error yang muncul saat urutan itu berubah.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Aturan hook ada dua: panggil hook hanya di tingkat teratas komponenmu, jangan pernah di dalam kondisi, perulangan, fungsi bersarang, atau setelah early return; dan panggil hook hanya dari function component atau dari custom hook. Aturan ini ada karena React membedakan hook berdasarkan urutan pemanggilannya, jadi urutan itu harus sama di setiap render.

React menyimpan nilai hook milik sebuah komponen di sebuah list yang melekat pada komponen itu. Di setiap render, React menelusuri list tersebut: pemanggilan hook pertama mendapat slot pertama, pemanggilan kedua mendapat slot kedua, dan seterusnya. Tidak ada nama, hanya posisi. Contoh di bawah adalah versi mainan dari ide itu dalam beberapa baris JavaScript biasa, bukan kode React yang asli, jadi kamu bisa merusaknya dengan aman.

Render pertama mengisi tiga slot: Ada, Countess, 36. Klik "Render without nickname": pemanggilan kondisional dilewati, jadi pemanggilan age mengambil slot 1 dan membaca Countess. React yang asli menyadari ketidakcocokan ini dalam beberapa kasus lalu melempar error, dan dalam kasus lain memberikan nilai yang salah ke hook yang salah, yang malah lebih buruk. Pindahkan pemanggilan toyUseState('Countess') ke atas if dan kedua tombol akan memberikan umur yang benar.

Dua aturan dalam kode

function Profile({ user }) {
    // Good: always called, always in this order
    const [tab, setTab] = useState('posts');
    const theme = useContext(ThemeContext);
    useEffect(() => {
        document.title = user.name;
    }, [user.name]);

    // Bad: inside a condition, a loop or a nested function
    if (user.isAdmin) {
        const [log, setLog] = useState([]); // breaks the order
    }
}

Pemanggilan hook yang selalu tercapai, dengan urutan yang sama, aman di mana pun letaknya di dalam badan komponen, asalkan muncul sebelum early return mana pun.

Memperbaiki hook yang dipanggil secara kondisional

Linter melaporkan ini sebagai React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Perbaikannya selalu sama: panggil hook setiap kali, dan taruh kondisinya di dalam hook.

// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
    if (user) {
        useEffect(() => {
            console.log('subscribe for', user.name);
        }, [user]);
    }
    return null;
}

Effect dideklarasikan di setiap render, dan if di dalamnya menentukan apakah ada yang perlu dikerjakan. Login dan logout beberapa kali, dan Console menampilkan satu subscribe untuk setiap login dan satu unsubscribe untuk setiap logout.

Hook setelah early return

Early return juga merupakan kondisi. Kode ini menghasilkan "Rendered more hooks than during the previous render" saat loading pertama kali menjadi false, karena render itu mencapai hook yang tidak pernah dipanggil oleh render sebelumnya. Saat sebuah render memanggil lebih sedikit hook daripada render sebelumnya, pesannya adalah "Rendered fewer hooks than expected".

// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
    if (loading) return <p>Loading...</p>;
    const [expanded, setExpanded] = useState(false);
    return <List items={comments} expanded={expanded} />;
}

Setiap hook berada di atas return, jadi kedua cabang memanggil hook yang sama. Klik "Toggle loading", buka daftarnya, lalu toggle loading dua kali: daftar itu ingat bahwa ia sedang terbuka, karena state-nya tidak pernah dilewati.

Hook di dalam perulangan

Kamu tidak bisa memanggil useState sekali per item di dalam perulangan, karena jumlah item bisa berubah. Beri setiap item komponennya sendiri dan panggil hook di sana; setiap instance punya slot-nya sendiri.

"Invalid hook call"

Pesannya diawali dengan "Invalid hook call. Hooks can only be called inside of the body of a function component." Ada tiga penyebab yang biasa:

  1. Hook berjalan saat React tidak sedang merender function component. Di dalam event handler, di dalam callback effect, di class component, atau di tingkat module. Pindahkan ke badan komponen atau ke custom hook, lalu panggil custom hook itu dari komponen.
  2. Versi react dan react-dom tidak sama. Instal versi yang sama untuk keduanya.
  3. Ada dua salinan React. Package lokal yang di-link atau library yang membundel React-nya sendiri membawa salinan kedua, dan hook dari satu salinan tidak bekerja dengan renderer dari salinan yang lain. Jalankan npm ls react dan pastikan hanya satu versi yang muncul.
npm ls react

Pratinjau di sini berjalan seperti build production, yang tidak mencetak pesan-pesan ini: hook yang dipanggil dari event handler gagal dengan TypeError biasa seperti "Cannot read properties of null", dan error milik React sendiri muncul sebagai kode singkat "Minified React error". Jalankan aplikasimu dalam mode development untuk membaca teks lengkapnya.

Biarkan linter yang menegakkannya

eslint-plugin-react-hooks memeriksa kedua aturan saat kamu mengetik, dan juga memeriksa array dependensi effect. Template React dari Vite dan konfigurasi ESLint dari Next.js sudah menyertakannya. Di konfigurasi ESLint flat, aktifkan preset recommended-nya:

// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';

export default defineConfig([reactHooks.configs.flat.recommended]);

Dua aturan intinya adalah react-hooks/rules-of-hooks (error) dan react-hooks/exhaustive-deps (peringatan). Sejak versi 7, preset itu juga mengaktifkan aturan yang berasal dari React Compiler, seperti react-hooks/purity dan react-hooks/refs, yang menandai render tidak murni dan pembacaan ref saat render. Untuk memilih aturan satu per satu, daftarkan plugin sebagai plugins: { 'react-hooks': reactHooks } lalu tulis aturan yang kamu inginkan.

Plugin ini mengenali hook dari namanya: setiap fungsi yang namanya diawali use lalu diikuti huruf kapital dianggap hook, dan hanya komponen (nama berhuruf kapital) serta fungsi use lainnya yang boleh memanggilnya. Itulah sebabnya custom hook harus memakai awalan tersebut.

Pengecualiannya: use

use, yang ditambahkan di React 19, membaca promise atau context, dan tidak seperti hook lainnya, hook ini boleh dipanggil di dalam kondisi atau perulangan. Pemanggilannya tetap harus terjadi saat render, di dalam komponen atau hook.

Hanya heading bertema yang berubah warna saat kamu mengganti tema. Coba hal yang sama dengan useContext di dalam if dan linter akan menandainya. Halaman hook use membahas cara membaca promise dengannya.

Pertanyaan yang sering diajukan

Apa saja aturan hook?

Ada dua aturan. Panggil hook hanya di tingkat teratas function component atau custom hook, jangan pernah di dalam kondisi, perulangan, fungsi bersarang, atau setelah early return. Dan panggil hook hanya dari function component atau custom hook, bukan dari fungsi biasa atau class component.

Mengapa hook tidak boleh dipanggil secara kondisional?

React tidak mengenal hook-mu berdasarkan nama. React menyimpan nilainya di sebuah list dan mengembalikannya sesuai urutan pemanggilan. Kalau sebuah kondisi melewatkan satu pemanggilan, setiap hook sesudahnya membaca nilai milik hook sebelumnya.

Apa arti "Rendered more hooks than during the previous render"?

Sebuah render memanggil lebih banyak hook daripada render sebelumnya, biasanya karena ada hook di dalam if atau setelah early return yang kali ini berjalan berbeda. Pindahkan setiap hook ke atas kondisi itu dan taruh kondisinya di dalam hook.

Bagaimana cara memperbaiki "Invalid hook call"?

Periksa tiga hal: hook berjalan saat React merender function component (bukan di event handler, callback effect, class component, atau tingkat module), versi react dan react-dom sama, dan aplikasi hanya punya satu salinan React (npm ls react menampilkan daftarnya).

Apakah hook use boleh dipanggil secara kondisional?

Boleh. use (React 19) adalah satu-satunya pengecualian: kamu bisa memanggilnya di dalam blok if dan perulangan. Hook ini tetap harus dipanggil di dalam komponen atau hook, bukan di event handler.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai