Bayangkan Anda sedang membangun sebuah aplikasi React yang tampak sederhana: hanya sebuah tombol dan angka yang bertambah setiap kali tombol diklik. Terlihat sepele, tetapi di balik interaksi kecil itu, ada satu konsep kunci yang bekerja keras mengatur data dan perubahan tampilan: useState.
Hook ini sering menjadi gerbang pertama bagi siapa pun yang belajar React modern. Namun, meski sintaksnya singkat, cara kerjanya dan cara menggunakannya secara tepat sering kali menyisakan tanda tanya. Apa sebenarnya yang dilakukan useState? Mengapa ada dua nilai yang dikembalikan? Kapan sebaiknya kita memisahkan atau menggabungkan state?
Dalam artikel “Mengenal useState: Teori Ringkas & Contoh Praktis” ini, kita akan mengurai useState dari dasar secara padat namun jelas, lalu langsung membawanya ke konteks dunia nyata lewat contoh-contoh kode yang dapat Anda adaptasi. Tanpa teori berbelit, tanpa istilah yang berlebihan-hanya konsep inti yang Anda butuhkan agar lebih percaya diri saat mengelola state di komponen React Anda.
Memahami Konsep Dasar useState dan Cara Kerjanya di Balik Layar
Kalau disederhanakan, useState adalah cara React menyimpan “ingatan” di dalam komponen fungsi. Setiap kali kamu memanggil useState, React akan membuat sebuah “slot” khusus di memori internalnya untuk menyimpan nilai saat ini, plus sebuah fungsi untuk memperbarui nilai itu. Di permukaan, kita cuma melihat pasangan value dan setter, tapi di balik layar React menyusun daftar state sesuai urutan pemanggilan hook. Artinya, urutan pemanggilan useState harus konsisten di setiap render-kalau di tengah-tengah kamu menambah atau menghapus hook secara kondisional, React bisa “kebingungan” mencocokkan slot state yang mana milik yang mana.
- Render pertama: React mendaftarkan semua state dan memberi nilai awal.
- Update state: React menyimpan nilai baru, menandai komponen untuk di-render ulang.
- Render ulang: React mengambil nilai dari slot yang sama, berdasarkan urutan hook.
| Istilah | Makna Singkat |
|---|---|
| State | Data dinamis milik komponen |
| Setter | Fungsi pengubah state |
| Re-render | Proses menggambar ulang UI |
Yang sering luput disadari adalah: memanggil fungsi setter dari useState tidak langsung mengubah tampilan saat itu juga, melainkan menjadwalkan pembaruan. React akan mengantre semua update, lalu menjalankan proses reconciliation untuk membandingkan “UI lama” dan “UI baru”, baru setelah itu DOM di-update seperlunya. Selain itu, jika kamu mengubah state berkali-kali dalam satu siklus, React bisa menggabungkannya (batching) supaya lebih hemat. Di sinilah pentingnya pola functional update ketika nilai barunya bergantung pada nilai sebelumnya; kamu memberi React petunjuk yang jelas tentang cara menghitung state baru, bukan sekadar menitipkan angka.
“`jsx
import { useState } from “react”;
function CounterCerdas() {
const [count, setCount] = useState(0);
const tambahTiga = () => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};
return (
Nilai sekarang: {count}
);
}
“`
Membedah Pola Umum dan Kasus Tersembunyi Saat Menggunakan useState
Kalau diperhatikan, sebagian besar penggunaan useState sebenarnya berputar di pola-pola yang mirip: menyimpan nilai input, toggle boolean, dan menyimpan data hasil fetch. Bedanya, kadang kita lupa kalau setiap pemanggilan setter itu sifatnya asinkron dan bisa saja terjadi beberapa kali dalam satu event. Ini sering memicu bug “nilai kok nggak ke-update ya?” yang susah dilacak. Pola aman yang sering dipakai developer berpengalaman biasanya mencakup:
- Selalu anggap state sebagai immutable (pakai spread atau metode serupa)
- Gunakan fungsi updater ketika state baru bergantung pada state lama
- Pecah state kompleks jadi beberapa state kecil kalau mulai susah dikontrol
- Hindari menyimpan data turunan (yang bisa dihitung dari state lain atau props)
Di balik pola umum itu, ada juga beberapa kasus “nyebelin” yang sifatnya lebih tersembunyi. Misalnya, state yang kelihatan tidak berubah karena closure lama di dalam event handler, atau re-render berulang karena kita menyimpan objek baru di state tanpa perlu. Contoh klasik lain: menyatukan terlalu banyak hal ke dalam satu objek state, lalu bingung sendiri saat perlu meng-update satu field saja. Untuk situasi semacam ini, sering kali solusi yang lebih rapi adalah dengan memecah state, atau bahkan memindahkan logika ke useReducer kalau sudah terlalu kompleks. Beberapa perbandingan singkatnya bisa dilihat di tabel berikut:
| Skenario | Pola useState Tepat | Alternatif |
|---|---|---|
| Counter sederhana | Satu state number | Fungsi updater |
| Form dengan banyak field | Beberapa state kecil | Atau satu objek + handler generik |
| Logika update rumit | Mulai terasa sulit diatur | useReducer |
| Data hasil fetch | data, loading, error |
Custom hook khusus fetch |
Strategi Penulisan State yang Bersih Terstruktur dan Mudah Dipelihara
Hal paling sederhana yang sering diabaikan saat bermain dengan useState adalah cara menamai dan mengelompokkan state. Nama yang jelas, konsisten, dan punya satu tanggung jawab bikin komponen jauh lebih enak dibaca. Hindari menjejalkan semua hal ke satu state besar, kecuali memang logis dijadikan satu objek. Biasakan diri untuk memikirkan: “nilai ini dipakai untuk apa?” lalu turunkan ke nama yang spesifik. Misalnya, bukan sekadar data, tapi userProfile atau cartItems. Pola kecil seperti ini kelihatannya sepele, tapi efeknya kerasa banget saat komponen mulai tumbuh dan kamu balik lagi baca kodenya tiga minggu kemudian.
- Gunakan satu state per tujuan yang jelas, jangan campur aduk.
- Manfaatkan fungsi updater saat state bergantung pada nilai sebelumnya.
- Kelompokkan state terkait ke dalam satu objek daripada banyak state terpisah.
- Ekstrak logika berulang ke custom hook untuk menghindari duplikasi.
| Anti-Pattern | Lebih Sehat |
|---|---|
| Nama state generik | Nama deskriptif & kontekstual |
| Set state bertubi-tubi tanpa pola | Kelompokkan dan gunakan updater |
| Logika state tersebar di mana-mana | Dipindah ke custom hook |
Untuk kasus di mana satu komponen mulai terasa “penuh” hanya karena kebanyakan state, ini biasanya sinyal untuk memecah komponen atau membuat custom hook. Misalnya ketika mengelola form yang agak kompleks: daripada menumpuk banyak useState untuk tiap field, kamu bisa menaruhnya dalam satu objek dan menyusun helper sederhana supaya update-nya tetap enak dipakai. Dengan begitu, logika seputar form terkumpul rapi, dan bagian JSX-nya fokus ke tampilan saja, bukan ke urusan internal state yang berantakan.
const [form, setForm] = useState({
name: "",
email: "",
age: "",
});
const handleChange = (field) => (event) => {
setForm((prev) => ({
...prev,
[field]: event.target.value,
}));
};
Rekomendasi Praktis Mengoptimalkan useState untuk Aplikasi Skala Kecil hingga Menengah
Di aplikasi skala kecil sampai menengah, kuncinya adalah menjaga state tetap ringkas, terstruktur, dan mudah ditebak. Hindari membuat terlalu banyak state yang tersebar di mana-mana; lebih baik gabungkan yang memang saling berkaitan, misalnya ke dalam satu objek atau gunakan custom hook. Beberapa kebiasaan yang cukup membantu:
- Kelompokkan state yang saling terkait (misalnya form, filter, atau data pagination).
- Gunakan nilai awal yang eksplisit supaya perilaku komponen lebih jelas.
- Minimalisir state turunan (derived state) dan hitung saja di dalam render jika masih ringan.
- Buat custom hook saat pola useState mulai berulang di beberapa komponen.
| Pola | Kapan Dipakai | Keuntungan |
|---|---|---|
useState tunggal |
UI simpel, 1-3 nilai state | Mudah dibaca & dirawat |
| State objek | Data saling terkait (mis. form) | Update lebih terstruktur |
| Custom hook | Logika berulang di banyak komponen | Reusable & rapi |
Selain soal struktur, cara mengupdate state juga penting supaya performa tetap stabil ketika aplikasi mulai tumbuh. Usahakan untuk menggabungkan beberapa update yang saling berkaitan dalam satu setState, dan manfaatkan functional update saat nilai baru bergantung pada nilai state sebelumnya. Kurangi penggunaan state untuk hal-hal yang bisa dihitung dari props atau state lain, dan jangan takut memecah komponen jika satu komponen mulai terasa “berat”. Untuk pola yang sering muncul, misalnya pengelolaan modal atau toggle, kamu bisa merapikannya seperti ini:
- Gunakan boolean sederhana untuk UI yang on/off seperti sidebar atau dropdown.
- Manfaatkan custom hook seperti
useToggle,useModalState, atauuseFormStateketika logika on/off atau form mulai berulang. - Pisahkan komponen jika satu komponen mengelola terlalu banyak state tidak terkait, supaya tiap bagian UI fokus pada satu tanggung jawab.
“`jsx
// contoh sederhana custom hook untuk toggle
import { useState, useCallback } from ‘react’;
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => {
setValue((prev) => !prev);
}, []);
return { value, toggle, setValue };
}
// penggunaan di komponen skala kecil/menengah
function Sidebar() {
const { value: isOpen, toggle } = useToggle();
return (
<>
{isOpen &&
}
>
);
}
“`
In Retrospect
Menutup perjalanan singkat kita bersama useState, bisa dibilang inilah “gerbang pertama” untuk benar-benar memahami cara React berpikir. Dengan sedikit teori dan beberapa contoh praktis, kamu sudah melihat bagaimana sebuah nilai bisa lahir, tumbuh, dan berubah di dalam komponen-bukan lagi sekadar konsep abstrak di dokumentasi.
Tentu, useState hanyalah satu keping kecil dari puzzle yang lebih besar. Setelah ini, kamu bisa mulai bereksperimen: mengelola lebih banyak state, memecah komponen, atau menggabungkannya dengan hook lain seperti useEffect. Semakin sering kamu bermain-main dengannya, semakin jelas pola dan “rasa” React akan terbentuk dengan sendirinya.
Pada akhirnya, kunci dari useState bukan hanya mengubah nilai, tetapi mengubah cara kita memikirkan alur data dalam aplikasi. Sisanya tinggal soal keberanian untuk mencoba, mengulang, dan memperbaiki. React menyediakan alatnya-giliran kamu yang menggunakannya.

