Fundamental React Hooks: useState, useEffect, useRef, useContext

Dokumentasi ini menjelaskan konsep dasar Hooks di React — cara komponen menyimpan dan mengelola data yang bisa berubah.


Kenapa React Butuh "State"?

Di React, komponen adalah function biasa yang me-return JSX. Tapi function biasa tidak bisa "ingat" nilai antar render — setiap kali function dipanggil ulang, semua variabel biasa di dalamnya di-reset dari awal.

Masalahnya: sering dibutuhkan komponen yang berubah tampilannya berdasarkan interaksi user (klik tombol, isi form, dll), dan nilai itu harus tetap tersimpan di antara render-render tersebut. Di sinilah state berperan — dan useState adalah cara paling dasar untuk membuatnya.


1. useState — Menyimpan Nilai yang Bisa Berubah

import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Jumlah: {count}</p>
      <button onClick={() => setCount(count + 1)}>Tambah</button>
    </div>
  )
}

Cara bacanya:

  • useState(0) → nilai awal-nya 0
  • count → nilai state saat ini (read-only, jangan diubah langsung)
  • setCount → fungsi khusus untuk mengubah nilai count

Poin paling penting: setiap kali setCount dipanggil, React akan render ulang komponen itu dengan nilai count yang baru. Kalau count = count + 1 diubah manual tanpa setCount, React tidak akan tahu ada perubahan, dan tampilan tidak akan ter-update.

Kenapa Tidak Pakai Variabel Biasa?

// ❌ SALAH — tidak akan bekerja
function Counter() {
  let count = 0

  return (
    <div>
      <p>Jumlah: {count}</p>
      <button onClick={() => count = count + 1}>Tambah</button>
    </div>
  )
}

Ini gagal karena:

  1. Mengubah let count tidak memberi tahu React untuk render ulang
  2. Setiap kali komponen di-render ulang (dari luar), count akan kembali ke 0

useState menyelesaikan dua masalah ini sekaligus: menyimpan nilai antar render + memicu render ulang otomatis saat nilainya berubah.


2. useEffect — Menjalankan Kode Setelah Render (Efek Samping)

Dipakai kalau dibutuhkan sesuatu di luar proses render biasa — misalnya fetch data dari API, pasang event listener, atau update judul halaman.

import { useState, useEffect } from 'react'

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data) => setUser(data))
  }, [userId]) // <- dependency array

  if (!user) return <p>Memuat...</p>
  return <p>Halo, {user.name}</p>
}

Bagian [userId] di akhir disebut dependency array — artinya "jalankan ulang effect ini hanya kalau userId berubah." Beberapa pola pentingnya:

Dependency Array Kapan Effect Dijalankan
useEffect(() => {...}) (tanpa array) Setiap kali komponen render — jarang dipakai, bisa infinite loop kalau ada setState di dalamnya
useEffect(() => {...}, []) Hanya sekali, saat komponen pertama kali muncul
useEffect(() => {...}, [userId]) Setiap kali userId berubah nilainya

Catatan penting untuk Next.js: di App Router, kalau sudah pakai Server Component (seperti route /users dan /roles yang langsung await prisma.findMany()), useEffect + useState untuk fetch data awal tidak dibutuhkan — itu tugasnya kalau kodenya jalan di Client Component (butuh 'use client' di baris paling atas file).


3. useRef — Menyimpan Nilai Tanpa Memicu Render Ulang

Mirip useState, tapi tidak membuat komponen render ulang saat nilainya berubah. Biasa dipakai untuk:

  • Akses langsung ke elemen HTML (misal fokus ke input)
  • Menyimpan nilai yang perlu "diingat" tapi tidak perlu ditampilkan
import { useRef } from 'react'

function TextInput() {
  const inputRef = useRef(null)

  const focusInput = () => {
    inputRef.current.focus()
  }

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Fokus ke Input</button>
    </div>
  )
}

4. useContext — Berbagi Data Tanpa "Prop Drilling"

Dipakai kalau banyak komponen bertingkat butuh data yang sama (misal data user yang sedang login), tanpa harus melempar props manual dari komponen induk ke turunannya satu per satu ("prop drilling").

import { createContext, useContext } from 'react'

const UserContext = createContext(null)

function App() {
  return (
    <UserContext.Provider value={{ name: 'Andi' }}>
      <Dashboard />
    </UserContext.Provider>
  )
}

function Dashboard() {
  const user = useContext(UserContext)
  return <p>Halo, {user.name}</p>
}

Tabel Ringkasan

Hook Fungsi
useState Menyimpan nilai yang bisa berubah dan memicu render ulang
useEffect Menjalankan efek samping (fetch data, event listener) setelah render
useRef Menyimpan nilai/akses elemen tanpa memicu render ulang
useContext Berbagi data antar komponen tanpa prop drilling

Aturan Penting Soal Hooks

  1. Hooks hanya bisa dipakai di Client Component — file harus punya 'use client' di baris paling atas (kecuali memakai Server Component murni tanpa interaktivitas)
  2. Tidak boleh dipanggil di dalam kondisi/loop — harus selalu di level paling atas function komponen
// ❌ SALAH
if (someCondition) {
  const [state, setState] = useState(0)
}
// ✅ BENAR
const [state, setState] = useState(0)
if (someCondition) {
  // pakai state di sini
}