- Pahami bagaimana `useState` menyimpan dan memperbarui status komponen lokal, termasuk pembaruan fungsional dan penanganan objek.
- Gunakan useEffect untuk efek samping dengan logika pengaturan/pembersihan yang jelas dan array dependensi yang akurat untuk menghindari kebocoran dan perulangan.
- Gabungkan `useState` dan `useEffect` untuk tugas-tugas dunia nyata seperti pengambilan data, langganan, dan pembaruan DOM dalam komponen fungsi.
- Ikuti aturan hooks dan perlakukan efek sebagai proses "setelah render" agar komponen React tetap mudah diprediksi dan dipelihara.
React hooks benar-benar mengubah cara kita menulis komponen., dan menguasai useState dan useEffect Pada dasarnya, ini adalah tiket masuk untuk menulis kode React modern. Jika Anda sudah menggunakannya tetapi masih terjebak dengan perulangan tak terbatas, state yang usang, atau array dependensi yang membingungkan, panduan ini akan membantu Anda menghubungkan semua bagian yang hilang dengan cara yang praktis.
Dalam artikel ini kita akan membahas secara mendalam cara menggunakan dengan benar. useState dan useEffect bersama, mengapa hook diperkenalkan sejak awal, aturan dan peringatan resminya, bagaimana dependensi sebenarnya bekerja di balik layar, jebakan umum yang merusak komponen Anda, dan pola yang telah teruji untuk efek samping, pembersihan, dan manajemen state dalam proyek nyata.
Mengapa hooks, dan mengapa secara khusus useState dan useEffect?
Hooks ditambahkan di React 16.8 untuk memungkinkan komponen fungsi menggunakan fitur state dan siklus hidup tanpa kelas.Sebelumnya, Anda harus menulis komponen kelas untuk menyimpan status lokal, berlangganan data eksternal, atau bereaksi terhadap peristiwa siklus hidup seperti pemasangan dan pelepasan.
Masalah besar dengan kelas adalah logika terkait sering kali terbagi di beberapa metode siklus hidup. seperti componentDidMount, componentDidUpdate dan componentWillUnmountPada akhirnya, Anda akan menemukan bagian-bagian dari fitur yang sama tersebar di berbagai metode berdasarkan ketika mereka berlari alih-alih apa Memang demikian, yang membuat kode lebih sulit dibaca, diuji, dan digunakan kembali.
Pengait membalik model ini: dengan useState Anda melampirkan state secara langsung ke komponen fungsi, dan dengan useEffect Anda melampirkan efek samping secara langsung ke logika yang membutuhkannya. Dengan begitu, Anda dapat mengelompokkan semua hal yang terkait dengan satu masalah di satu tempat dan dengan mudah mengekstrak kait (hooks) yang dapat digunakan kembali di kemudian hari.
Di antara semua kaitan, useState dan useEffect adalah primitif intiAnda dapat membangun sebagian besar fitur sehari-hari hanya dengan dua hal ini: status UI seperti formulir dan tombol, permintaan jaringan, langganan, pengatur waktu, pembaruan DOM, dan banyak lagi. Hook lainnya (useRef, useReducer, useContext, useMemo…) memang bagus, tetapi semuanya dibangun di atas ide yang sama.
Aturan React Hooks yang tidak boleh Anda langgar
React hooks memiliki beberapa aturan ketat yang membuatnya berfungsi dengan andal di berbagai proses rendering.Jika Anda melanggar aturan tersebut, Anda akan melihat kesalahan saat runtime atau bug yang sangat halus dan sulit untuk di-debug.
Aturan pertama: panggil hook hanya di dalam komponen fungsi React atau hook kustom.Anda tidak dapat menggunakan useState or useEffect dalam komponen kelas, fungsi utilitas reguler, atau di luar komponen apa pun. Pola seperti ini tidak valid:
import React, { Component, useState } from 'react';
class App extends Component {
// ❌ This will throw - hooks don’t work in classes
const = useState(0);
render() {
return <h1>Hello, I am a Class Component!</h1>;
}
}
Pendekatan yang tepat adalah beralih ke komponen fungsi jika Anda ingin menggunakan hook.:
import React, { useState } from 'react';
function App() {
const = useState('');
return (
<div>
Your JSX code goes in here...
</div>
);
}
export default App;
Aturan kedua: panggil hook hanya di level teratas komponen Anda.Artinya, tidak ada hook di dalam loop, kondisi, atau fungsi bersarang. React mengandalkan pemanggilan hook dalam urutan yang sama pada setiap render untuk "mencocokkan" setiap elemen. useState dan useEffect memanggil dengan data yang tersimpan, jadi ini tidak valid:
function BadComponent({ enabled }) {
if (enabled) {
// ❌ Wrong: hook inside a conditional
const = useState(0);
}
// ...
}
Sebaliknya, deklarasikan hook tanpa syarat di bagian atas dan gunakan kondisi di dalam efek atau JSX.Hook tersebut harus selalu dipanggil, tetapi logika yang dijalankannya dapat bersifat kondisional:
function ConditionalEffectComponent() {
const = useState(false);
useEffect(() => {
if (isMounted) {
console.log('Component mounted');
}
}, );
return (
<div>
<button onClick={() => setIsMounted(!isMounted)}>
{isMounted ? 'Unmount' : 'Mount'}
</button>
</div>
);
}
Aturan tersirat ketiga adalah bahwa hook harus diimpor dari React (atau pustaka hook), bukan diimplementasikan secara ad-hoc.Ini sudah jelas, tetapi perlu disebutkan: keajaibannya terletak pada pengatur hook internal React yang melacak panggilan hook di seluruh proses render.
Mengelola state lokal dengan benar menggunakan useState
useState Memungkinkan Anda untuk melampirkan state ke komponen fungsi dan menerima nilai saat ini serta fungsi pembaruan.Secara konseptual, ini adalah padanan fungsional dari this.state dan this.setState dalam komponen kelas.
Contoh tandingan minimal dengan useState terlihat seperti ini:
import React, { useState } from 'react';
function Counter() {
const = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
Saat Anda menelepon useState(initialValue)React menyimpan status tersebut dan mengembalikan pasangan.: nilai status saat ini dan sebuah setter. Tidak seperti variabel lokal biasa, status tetap ada di seluruh proses rendering, sehingga count Nilai tidak akan direset ke 0 setiap kali fungsi komponen dijalankan.
Anda dapat menggunakan nilai apa pun yang dapat diserialisasi untuk status: angka, string, boolean, array, objek, dan bahkan fungsi.. Anda juga dapat menelepon useState beberapa kali dalam komponen yang sama untuk menjaga agar nilai-nilai terkait tetap terpisah, alih-alih memasukkan semuanya ke dalam satu objek.
Ketika nilai status baru bergantung pada nilai status sebelumnya, selalu gunakan bentuk pembaruan fungsional.Hal ini menghindari bug ketika beberapa pembaruan status terjadi secara beruntun dengan cepat:
setCount(prev => prev + 1);
Detail lain yang halus namun penting adalah bahwa memanggil setter akan mengganti seluruh nilai state, bukan menggabungkan objek seperti this.setState di kelasJika status Anda berupa objek atau array, Anda perlu menyebarkan nilai sebelumnya sendiri:
const = useState({ name: 'Alex', age: 30 });
// ✅ Correct: copy and update
setUser(prev => ({ ...prev, age: prev.age + 1 }));
Untuk nilai awal yang mahal, Anda dapat melakukan inisialisasi status secara bertahap dengan memberikan fungsi ke useStateReact hanya akan memanggilnya pada render pertama:
const = useState(() => calculateInitialValue());
Mengatasi efek samping dengan useEffect
useEffect adalah API React untuk menjalankan efek samping dalam komponen fungsi.“Efek samping” adalah segala sesuatu yang berhubungan dengan dunia luar: pengambilan data, pencatatan log, perubahan DOM langsung, langganan, pengatur waktu, API peramban, dll.
Secara konseptual, useEffect menggantikan kombinasi dari componentDidMount, componentDidUpdate dan componentWillUnmount dari komponen kelasAlih-alih membagi satu efek ke dalam tiga metode siklus hidup, Anda mendeklarasikannya sekali dan membiarkan React menangani kapan efek tersebut dijalankan dan kapan efek tersebut dibersihkan.
Tanda tangan dasarnya adalah useEffect(setup, dependencies?). itu setup Fungsi tersebut adalah isi efek Anda; secara opsional dapat mengembalikan fungsi pembersihan. dependencies Array tersebut memberi tahu React kapan efek perlu dijalankan kembali.
useEffect(() => {
// side effect logic here
return () => {
// optional cleanup logic here
};
}, );
Secara default, tanpa argumen kedua, efek akan berjalan setelah setiap rendering. (pemasangan pertama dan setiap pembaruan berikutnya). Itu seringkali terlalu berat untuk permintaan jaringan atau logika yang rumit.
Pola yang sangat umum adalah memperbarui sesuatu yang eksternal setiap kali sebagian dari status berubah.Misalnya, memperbarui judul halaman berdasarkan jumlah klik:
import React, { useState, useEffect } from 'react';
function Counter() {
const = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, ); // effect re-runs only when `count` changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
Array dependensi sangat penting untuk performa dan keakuratan.Ini mengontrol kapan React harus menjalankan kembali efek tersebut: jika ada dependensi yang berubah sesuai dengan Object.is Sebagai perbandingan, efek tersebut dibersihkan dan dijalankan lagi; jika tidak ada yang berubah, maka akan dilewati.
Memahami array dependensi seperti seorang profesional.
Array dependensi adalah tempat sebagian besar hal yang paling rumit terjadi. useEffect Serangga berasal dariReact membandingkan setiap elemen array dengan nilai sebelumnya menggunakan Object.isJika semua nilai sama, efek dilewati; jika setidaknya satu nilai berbeda, efek dijalankan kembali.
Ada tiga konfigurasi dependensi utama yang akan Anda gunakan sepanjang waktu.:
- Tidak ada argumen keduaEfek ini berjalan setelah setiap rendering.
- Array kosong
[]Efek ini hanya berjalan sekali saat komponen dipasang (mount) dan dibersihkan saat komponen dilepas (unmount). - Array dengan nilai
Efek ini berjalan setelah proses mount dan setiap kali ada perubahan dependensi.
Jika dependensi berupa nilai primitif (angka, string, boolean), ini mudah dilakukan.Masalah mulai muncul ketika Anda memasukkan objek, array, atau fungsi ke dalam dependensi, karena kesamaan didasarkan pada referensi. Dua objek identik dengan referensi berbeda dianggap "berbeda", yang menyebabkan pengulangan proses pada setiap rendering.
Pertimbangkan suatu efek yang bergantung pada team objek dari properti:
function Team({ team }) {
useEffect(() => {
console.log(team.id, team.active);
}, ); // ⚠️ might re-run every render if `team` reference changes
}
Meskipun konten tim sebenarnya tidak berubah, referensi objek baru pada setiap rendering akan memaksa efek tersebut untuk dijalankan kembali.Untuk menghindari hal ini, Anda dapat bergantung pada field primitif yang sebenarnya Anda gunakan, atau merekonstruksi objek di dalam efek itu sendiri.
Versi yang lebih aman hanya melacak apa yang benar-benar dibutuhkan oleh efek tersebut.:
function Team({ team }) {
const { id, active } = team;
useEffect(() => {
console.log(id, active);
}, );
}
Jika Anda benar-benar membutuhkan seluruh objek di dalam efek, Anda dapat membuatnya ulang di sana daripada menggunakannya sebagai dependensi.Dengan begitu, daftar dependensi masih dapat didasarkan pada tipe data primitif:
function Team({ team }) {
const { id, active, name } = team;
useEffect(() => {
const localTeam = { id, active, name };
// use `localTeam` here
}, );
}
Sebagai upaya terakhir, Anda dapat menggunakan memoisasi dengan useMemo or useCallback untuk barang atau fungsi yang mahalNamun, ingatlah bahwa memoisasi itu sendiri memiliki biaya. Jangan menggunakannya di mana-mana "hanya untuk berjaga-jaga"; tambahkan memoisasi ketika dependensi tertentu benar-benar menyebabkan masalah kinerja.
Membersihkan efek dengan benar
Beberapa efek samping mengalokasikan sumber daya yang harus dilepaskan.: langganan, soket, interval, batas waktu, pendengar peristiwa, dll. Lupa membersihkannya dapat dengan mudah menyebabkan kebocoran memori atau pekerjaan yang berulang.
In useEffectPembersihan ditangani dengan mengembalikan fungsi dari efek tersebut.React akan memanggil fungsi ini sebelum menjalankan efek lagi dengan dependensi baru, dan juga untuk terakhir kalinya saat komponen dilepas (unmount).
import { useEffect } from 'react';
function LogMessage({ message }) {
useEffect(() => {
const log = setInterval(() => {
console.log(message);
}, 1000);
return () => {
clearInterval(log);
};
}, );
return <div>logging to console "{message}"</div>;
}
Dalam contoh ini, setiap kali message Saat terjadi perubahan, React pertama-tama menghapus interval lama, kemudian mengatur interval baru dengan pesan yang diperbarui.Saat komponen menghilang dari UI, pembersihan terakhir akan menghapus interval tersebut secara permanen.
Penggabungan “penyiapan + pembersihan” ini merupakan inti dari model mental useEffectCobalah untuk menganggap setiap efek sebagai proses mandiri yang dimulai di fungsi setup dan berhenti sepenuhnya di fungsi cleanup. React mungkin menjalankan beberapa siklus setup/cleanup dalam pengembangan (terutama dalam Strict Mode) untuk menguji apakah fungsi cleanup benar-benar membatalkan semuanya.
Contoh klasiknya adalah berlangganan ke sumber eksternal, seperti API obrolan atau peristiwa peramban (lihat menangani onKeyDown di React):
useEffect(() => {
function handleClick(event) {
console.log('Clicked', event.clientX, event.clientY);
}
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
}, []); // runs once on mount, cleans up on unmount
Menggunakan useState dan useEffect secara bersamaan untuk pengambilan data.
Salah satu kombinasi yang paling umum di dunia nyata adalah menggunakan useState dan useEffect untuk mengambil data dari APIAnda menyimpan data (dan mungkin flag pemuatan/kesalahan) dalam state, dan melakukan permintaan dalam sebuah effect yang berjalan saat komponen dipasang atau saat beberapa parameter berubah.
Pola dasar untuk mengambil data sekali saat komponen dipasang (mount) terlihat seperti ini.:
import { useEffect, useState } from 'react';
function FetchItems() {
const = useState([]);
useEffect(() => {
let ignore = false;
async function fetchItems() {
try {
const response = await fetch('/items');
const fetchedItems = await response.json();
if (!ignore) {
setItems(fetchedItems);
}
} catch (error) {
console.error('Error fetching items:', error);
}
}
fetchItems();
return () => {
// avoid updating state if the component unmounted
ignore = true;
};
}, []);
return (
<div>
{items.map(item => (
<div key={item.id ?? item}>{item.name ?? item}</div>
))}
</div>
);
}
Di sini, array dependensi kosong memastikan bahwa permintaan dijalankan tepat sekali.. Internal ignore flag adalah cara sederhana untuk menghindari pengaturan state pada komponen yang belum dipasang (unmounted) jika permintaan tersebut terlambat diselesaikan.
Sangat umum juga untuk menambahkan flag loading dan menampilkan spinner atau placeholder saat data sedang diproses.:
const Statistics = () => {
const = useState([]);
const = useState(true);
useEffect(() => {
const getStats = async () => {
try {
const statsData = await getData();
setStats(statsData);
} finally {
setLoading(false);
}
};
getStats();
}, []);
if (loading) {
return <div>Loading statistics...</div>;
}
return (
<ul>
{stats.map(stat => (
<li key={stat.id}>{stat.label}: {stat.value}</li>
))}
</ul>
);
};
Jika kueri Anda bergantung pada suatu parameter (seperti kategori, filter, atau parameter rute), tambahkan parameter tersebut ke dalam array dependensi. Jadi efeknya akan terulang kembali ketika terjadi perubahan:
useEffect(() => {
async function fetchItems() {
const response = await fetch(`/items?category=${category}`);
const data = await response.json();
setItems(data);
}
fetchItems();
}, );
Berpikir dalam konteks “efek pada setiap rendering” versus “siklus hidup”
Jika Anda terbiasa dengan komponen kelas, mungkin akan tergoda untuk memetakannya secara mental. useEffect metode pemasangan/pembaruan/pelepasanNamun, hal itu biasanya malah menimbulkan lebih banyak kebingungan. Model mental yang lebih sederhana adalah: "efek dijalankan setelah rendering, dan mungkin dibersihkan sebelum dijalankan berikutnya".
Di kelas, Anda sering kali harus menduplikasi logika antara satu kelas dengan kelas lainnya. componentDidMount dan componentDidUpdate Karena Anda menginginkan efek yang sama berjalan baik saat komponen dipasang (mount) maupun saat pembaruan (update). Dengan hooks, duplikasi tersebut hilang: satu efek mencakup kedua kasus, dan React akan menangani pembersihan di antara eksekusi.
Desain ini juga menghilangkan seluruh kelas bug yang berkaitan dengan kegagalan menangani pembaruan dengan benar.Misalnya, dalam komponen kelas yang berlangganan status online teman, mudah untuk lupa berlangganan kembali ketika... props.friend perubahan, menyebabkan langganan kedaluwarsa atau kerusakan saat dilepas. Dengan useEffect yang mencantumkan friend.id Sebagai dependensi, React akan secara otomatis menjalankan pembersihan untuk "teman lama" dan pengaturan untuk "teman baru".
Perlu diingat bahwa dalam mode pengembangan Strict Mode, React sengaja menjalankan siklus setup + cleanup Anda dua kali saat komponen dipasang (mount).Hal ini tidak terjadi dalam produksi, tetapi ini merupakan uji stres yang berguna untuk memastikan bahwa pembersihan Anda benar-benar membatalkan semuanya dan efek Anda dapat dijalankan dengan aman beberapa kali.
Mengoptimalkan dan memecahkan masalah perilaku useEffect
Ketika suatu efek berjalan lebih sering dari yang Anda duga, hal pertama yang perlu diperiksa adalah array dependensi.Entah dependensi berubah setiap kali rendering (umum terjadi pada objek/fungsi inline) atau Anda lupa menentukan array sama sekali.
Mencatat nilai-nilai dependensi adalah cara cepat untuk melakukan debugging.:
useEffect(() => {
console.log('Effect deps:', dep1, dep2);
}, );
Jika Anda melihat log yang berbeda setiap kali, periksa dependensi mana yang sebenarnya berubah.Seringkali Anda akan menemukan objek inline atau fungsi panah yang dibuat ulang setiap kali rendering. Memindahkan pembuatan objek ke dalam efek, atau mengangkat fungsi ke luar komponen, atau melakukan memoisasi dengan useCallback dapat menstabilkan ketergantungan bila diperlukan.
Perulangan tak terbatas terjadi ketika suatu efek bergantung pada suatu nilai dan secara tanpa syarat memperbarui nilai yang sama tersebut.. Sebagai contoh:
useEffect(() => {
setCount(count + 1); // ⚠️ will cause a loop if `count` is a dependency
}, );
Tiap kali count perubahan, efeknya berjalan, pembaruan count Sekali lagi, memicu rendering lain, dan seterusnya.Untuk mematahkan pola tersebut, pertimbangkan apakah pembaruan status benar-benar termasuk dalam sebuah efek, apakah seharusnya dipicu oleh interaksi pengguna, atau apakah Anda dapat mengandalkan nilai yang berbeda.
Terkadang Anda ingin membaca nilai terbaru dari beberapa state atau props di dalam sebuah effect tanpa nilai tersebut memicu pengulangan eksekusi.Dalam skenario tingkat lanjut tersebut, API yang lebih baru seperti “effect events” (melalui useEffectEvent (dalam dokumentasi React) atau ref dapat membantu, tetapi untuk sebagian besar kasus praktis, tetap setia pada dependensi lebih aman dan sederhana.
Menggabungkan semuanya, menggunakan useState dan useEffect Intinya, hal itu bermuara pada beberapa kebiasaan mendasar.: Jaga agar state tetap kecil dan terfokus, utamakan pembaruan fungsional saat menurunkan state baru dari state lama, strukturkan efek di sekitar pasangan setup/cleanup, jujur dan eksplisit dengan array dependensi, dan selalu hormati aturan hook agar React dapat melacak dengan andal apa yang termasuk di mana. Ketika Anda mengikuti prinsip-prinsip tersebut, komponen Anda tetap mudah diprediksi, efek samping Anda berperilaku baik, dan basis kode React Anda menjadi jauh lebih mudah untuk dikembangkan seiring pertumbuhan aplikasi Anda.
