Kamu Pasti Paham useEffect React Hook Setelah Lihat Contoh Ini
Pernah nggak sih kamu lagi asyik ngoding React, terus tiba-tiba ketemu masalah yang nggak bisa diselesaikan cuma pakai useState
atau props
doang? Misalnya, kamu perlu ngambil data dari API pas komponen pertama kali muncul, atau kamu mau update judul halaman tiap kali ada data baru, atau bahkan kamu mau nambahin event listener pas user klik sesuatu, terus pas komponennya ilang, event listener-nya juga ikutan ilang biar nggak memory leak? Nah, di sinilah useEffect
React Hook jadi pahlawan super kamu!
Banyak banget developer pemula yang bingung sama useEffect
. Konsepnya memang agak sedikit beda dari yang biasa, apalagi kalau kamu sebelumnya cuma kenal class component. Tapi tenang aja, di artikel ini kita bakal kupas tuntas useEffect
sampai kamu benar-benar paham, lengkap dengan contoh-contohnya yang gampang dicerna. Anggap aja ini obrolan santai antar teman yang lagi belajar bareng. Siap? Yuk, kita mulai!
useEffect
Itu Apa Sih Sebenarnya? Kenapa Penting?
Oke, pertama-tama, apa sih useEffect
itu? Simpelnya, useEffect
itu salah satu React Hook yang khusus diciptakan buat menangani "side effects" di dalam functional component kamu. Loh, "side effects" itu apa lagi?
Bayangin gini: React itu didesain biar UI kamu jadi fungsi murni (pure function) dari props
dan state
. Artinya, kalau props
atau state
-nya sama, output UI-nya juga harusnya sama persis. Ini bagus banget buat bikin kode yang prediktif dan gampang dites.
Tapi, dunia nyata kan nggak se-pure itu. Kadang kamu perlu ngelakuin hal-hal di luar lingkup "render" UI yang murni. Nah, inilah yang disebut "side effects". Contoh side effects itu banyak banget, misalnya:
- Mengambil data (data fetching) dari API. Ini paling sering.
- Mengatur subscription (misalnya ke WebSocket atau Redux store).
- Memanipulasi DOM secara langsung (misalnya mengubah
document.title
). - Mengatur timer (
setTimeout
,setInterval
). - Mengakses local storage.
Dulu, di class component, side effects ini diurus pakai lifecycle methods kayak componentDidMount
, componentDidUpdate
, atau componentWillUnmount
. Nah, useEffect
ini adalah versi modern dan lebih fleksibel dari gabungan ketiga lifecycle method itu di dunia functional component. Tujuannya satu: menjaga agar komponen kamu tetap bersih dari logika side effect yang berantakan, dan mengumpulkannya di satu tempat yang terorganisir.
Cara Pakai useEffect
yang Paling Dasar
Struktur dasar useEffect
itu gampang banget, cuma butuh dua argumen:
javascript
useEffect(callbackFungsi, [arrayDependencies]);
callbackFungsi
(fungsi pertama): Ini adalah fungsi yang bakal dieksekusi sama React. Di sinilah kamu masukin semua kode side effect kamu. Fungsi ini bisa bersifat sinkronus, tapi kalau kamu mau async (misalnya fetch data), kamu harus panggil fungsi async di dalamnya atau definisikan fungsi async-nya di dalam callback. Kita bahas nanti contohnya.[arrayDependencies]
(array kedua, opsional): Ini yang paling penting dan sering bikin orang bingung. Array ini berisi daftar nilai-nilai (bisaprops
,state
, atau variabel lain) yang "diamati" sama React.useEffect
akan menjalankan ulangcallbackFungsi
-nya cuma kalau ada nilai di dalam array ini yang berubah dari render sebelumnya. Kalau array ini nggak kamu sertakan sama sekali,useEffect
bakal jalan setiap kali komponen di-render ulang. Kalau array ini kosong ([]
),useEffect
cuma jalan sekali pas komponen pertama kali muncul, miripcomponentDidMount
.
Oke, biar kebayang, kita lihat contoh paling dasar:
jsx
import React, { useEffect, useState } from 'react';function MyComponent() {
const [count, setCount] = useState(0);// Contoh 1: Tanpa array dependencies (berjalan setiap render)
useEffect(() => {
console.log('Komponen di-render atau state/props berubah!', count);
}); // Tidak ada array dependencies// Contoh 2: Dengan array dependencies kosong (berjalan sekali saat mount)
useEffect(() => {
console.log('Komponen pertama kali muncul di layar!');
// Biasanya buat fetch data awal
}, []); // Array dependencies kosong// Contoh 3: Dengan array dependencies berisi nilai (berjalan saat nilai berubah)
useEffect(() => {
console.log('Nilai count berubah jadi:', count);
}, [count]); // Berjalan hanya jika 'count' berubahreturn (
Contoh useEffect
Count: {count}
setCount(prevCount => prevCount + 1)}>
Tambah Count
setCount(0)}>Reset Count
);
}
Dari contoh di atas, kamu bisa lihat bedanya:
useEffect
tanpa array dependencies: Tiap kamu klik tombol "Tambah Count", atau "Reset Count", bahkan kalau adaprops
lain yang berubah yang bikin komponen ini di-render ulang,console.log
yang pertama bakal muncul. Ini jarang banget dipakai karena bisa bikin performa jadi jelek kalau side effect-nya berat.useEffect
dengan[]
: Kalau kamu refresh browser atau komponenMyComponent
ini baru muncul,console.log
yang kedua cuma bakal muncul sekali aja. Setelah itu, maucount
berubah berapa kalipun, dia nggak akan jalan lagi. Cocok buat inisialisasi awal.useEffect
dengan[count]
:console.log
yang ketiga cuma akan muncul kalau nilaicount
berubah. Kalau adastate
lain di komponen ini yang berubah tapicount
nggak,useEffect
yang ini nggak akan jalan.
Paham kan bedanya? Ini kunci banget buat menguasai useEffect
.
Kapan Array Dependencies Jadi Kritis? (Dan Kenapa []
Itu Penting)
Ini dia bagian yang paling sering bikin pusing tapi justru paling powerful: array dependencies.
1. useEffect
tanpa array dependencies (No Dependency Array)
Kalo kamu nggak kasih array dependencies sama sekali, useEffect
bakal jalan setiap kali komponen di-render ulang. Ini mirip gabungan componentDidMount
dan componentDidUpdate
di class component.
javascript
useEffect(() => {
// Ini akan berjalan SETIAP kali komponen di-render ulang
// Jadi harus hati-hati, bisa bikin infinite loop kalau kamu update state di sini
console.log('Saya selalu muncul!');
});
Contoh: Kalau kamu fetch data di sini, bayangin tiap user ngetik sesuatu di input field, data bakal di-fetch ulang terus. Boros resource dan bisa bikin error. Hindari sebisa mungkin, kecuali kamu memang butuh efek yang dieksekusi setiap render.
2. useEffect
dengan array dependencies kosong ([]
)
Ini artinya useEffect
bakal jalan hanya sekali saja, setelah render pertama komponen. Mirip banget sama componentDidMount
.
javascript
useEffect(() => {
// Ini hanya akan berjalan SATU KALI setelah komponen pertama kali muncul
console.log('Saya muncul cuma sekali lho, pas pertama lahir!');
// Contoh umum: fetching data awal dari API
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
}, []); // Perhatikan array kosong di sini
Kapan ini berguna? Saat kamu perlu inisialisasi sesuatu yang cuma perlu dilakukan sekali aja, kayak:
- Ngambil data awal dari server.
- Nambahin event listener global (misalnya untuk keyboard shortcut).
- Mengatur langganan ke service eksternal yang cuma perlu diatur sekali.
3. useEffect
dengan array dependencies berisi nilai ([dep1, dep2, ...]
)
Ini yang paling sering dipakai dan paling fleksibel. useEffect
bakal jalan hanya jika salah satu nilai di dalam array dependencies berubah dari render sebelumnya.
javascript
import React, { useEffect, useState } from 'react';function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);useEffect(() => {
setLoading(true);
// Kita anggap ini simulasi fetch data dari API
console.log(Mengambil data untuk user ID: ${userId}...);
fetch(https://api.example.com/users/${userId})
.then(res => res.json())
.then(data => {
setUserData(data);
setLoading(false);
})
.catch(error => {
console.error('Error fetching user data:', error);
setLoading(false);
});
}, [userId]); // Efek ini akan berjalan setiap kali 'userId' berubahif (loading) {
return Loading user data...;
}if (!userData) {
return Tidak ada data user.;
}return (
{userData.name}
Email: {userData.email}
ID: {userData.id}
);
}
Di contoh UserProfile
di atas:
- Ketika komponen
UserProfile
pertama kali di-render,useEffect
akan jalan dan mengambil data untukuserId
yang pertama. - Jika
userId
yang dikirim sebagaiprop
dari komponen induk berubah (misalnya user klik tombol untuk melihat profil user lain),useEffect
akan mendeteksi perubahanuserId
dan akan menjalankan ulang fungsi fetch data-nya. Ini otomatis dan efisien!
Penting: Selalu pastikan semua nilai yang digunakan di dalam callbackFungsi
useEffect
yang bisa berubah dan membuat efek perlu dijalankan ulang dimasukkan ke dalam array dependencies. Kalau tidak, kamu akan menghadapi masalah yang namanya "stale closure", di mana fungsi useEffect
kamu "melihat" nilai state
atau props
yang lama. ESLint biasanya akan ngasih peringatan kalau kamu lupa nambahin dependencies. Dengarkan dia!
Fungsi Cleanup: Jangan Lupa Bersih-Bersih!
Bayangin kamu pakai kamar hotel. Abis pakai, kamu pasti beresin barang bawaan kamu kan? Nggak dibiarkan berantakan gitu aja. Nah, useEffect
juga punya konsep bersih-bersih yang sama, namanya fungsi cleanup.
Terkadang, side effects yang kamu buat perlu "dibersihkan" saat komponen nggak lagi dipakai, atau sebelum useEffect
dieksekusi ulang. Kalau nggak dibersihin, bisa terjadi memory leak atau perilaku aplikasi yang aneh.
useEffect
memungkinkan kamu mengembalikan sebuah fungsi dari callbackFungsi
-nya. Fungsi yang dikembalikan inilah yang disebut fungsi cleanup. Kapan fungsi cleanup ini dieksekusi?
- Sebelum
useEffect
dieksekusi ulang (jika dependencies berubah). - Ketika komponen di-unmount (dihilangkan dari DOM).
Contoh yang paling sering: setInterval
dan addEventListener
.
jsx
import React, { useEffect, useState } from 'react';function Timer() {
const [seconds, setSeconds] = useState(0);useEffect(() => {
// Setiap 1 detik, tambahin 'seconds'
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);// Fungsi cleanup: Ini akan jalan pas komponen di-unmount atau sebelum efek dieksekusi ulang
return () => {
console.log('Membersihkan interval...');
clearInterval(intervalId); // Penting! Hentikan interval
};
}, []); // Array kosong, artinya efek ini jalan sekali dan cleanup juga sekali pas unmountreturn (
Timer: {seconds} detik
Lihat console untuk pesan cleanup.
);
}function App() {
const [showTimer, setShowTimer] = useState(true);return (
Aplikasi Utama
setShowTimer(!showTimer)}>
{showTimer ? 'Sembunyikan Timer' : 'Tampilkan Timer'}
{showTimer && }
);
}
Coba jalankan kode di atas. Ketika kamu klik "Sembunyikan Timer", komponen Timer
akan di-unmount, dan kamu akan melihat pesan "Membersihkan interval..." di console. Ini penting banget karena kalau clearInterval
nggak dipanggil, setInterval
akan terus berjalan di background meskipun komponen Timer
sudah nggak ada, yang bisa menyebabkan memory leak dan error.
Contoh lain dengan addEventListener
:
jsx
import React, { useEffect } from 'react';function MouseTracker() {
useEffect(() => {
const handleMouseMove = (event) => {
console.log(Mouse bergerak di X: ${event.clientX}, Y: ${event.clientY});
};window.addEventListener('mousemove', handleMouseMove);return () => {
// Pastikan listener dihapus saat komponen di-unmount
window.removeEventListener('mousemove', handleMouseMove);
console.log('Event listener mousemove dihapus.');
};
}, []); // [] agar listener cuma ditambah sekali
return Gerakkan mouse di atas halaman dan lihat console.;
}
Sama seperti setInterval
, removeEventListener
itu krusial. Tanpa itu, handleMouseMove
akan terus dipanggil meskipun MouseTracker
sudah nggak ada di layar, yang lagi-lagi bisa bikin memory leak dan masalah lainnya.
Contoh Nyata useEffect
yang Sering Kamu Temui
Oke, biar makin mantap, kita bahas beberapa skenario useEffect
yang paling sering kamu temuin di proyek React:
1. Mengambil Data dari API (Data Fetching)
Ini juara satu! Hampir semua aplikasi modern butuh ngambil data dari backend.
jsx
import React, { useEffect, useState } from 'react';function PostDetail({ postId }) {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);useEffect(() => {
const fetchPost = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(https://jsonplaceholder.typicode.com/posts/${postId});
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
const data = await response.json();
setPost(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};if (postId) { // Pastikan postId ada sebelum fetch
fetchPost();
}// Tidak ada cleanup spesifik untuk fetch API, karena HTTP request biasanya sekali jalan
// kecuali kamu pakai WebSocket atau long polling
}, [postId]); // Efek akan jalan ulang setiap kali postId berubahif (loading) return Loading post...;
if (error) return Error: {error.message};
if (!post) return Pilih Post ID untuk menampilkan detail.;return (
{post.title}
{post.body}
);
}// Cara pakainya di komponen lain:
function App() {
const [currentPostId, setCurrentPostId] = useState(1);return (
Contoh Fetch Data dengan useEffect
setCurrentPostId(1)}>Lihat Post 1
setCurrentPostId(2)}>Lihat Post 2
setCurrentPostId(3)}>Lihat Post 3
);
}
Perhatikan:
- Kita menggunakan
async/await
di dalam fungsifetchPost
yang kita definisikan di dalamuseEffect
. Kenapa nggak langsungasync useEffect(...)
? KarenauseEffect
callback tidak boleh langsungasync
. [postId]
di dependencies memastikan fetch ulang terjadi hanya ketikapostId
berubah. Keren kan?
2. Mengubah Judul Dokumen (Document Title)
Contoh simpel DOM manipulation.
jsx
import React, { useEffect, useState } from 'react';function PageTitleUpdater() {
const [pageName, setPageName] = useState('Home');useEffect(() => {
document.title = Selamat Datang di ${pageName};
console.log('Judul halaman diubah menjadi:', document.title);
}, [pageName]); // Efek ini akan jalan setiap kali 'pageName' berubahreturn (
Update Judul Halaman
Judul halaman saat ini: {document.title}
setPageName('About Us')}>Ke Halaman About Us
setPageName('Produk')}>Ke Halaman Produk
);
}
Setiap kali pageName
berubah, document.title
akan langsung diupdate.
Jebakan Batman (Pitfalls
) dan Tips Terbaik (Best Practices
)
Meskipun powerful, useEffect
punya beberapa jebakan yang sering bikin pusing kalau kamu nggak hati-hati.
1. Masalah Dependencies: Si Stale Closure dan Infinite Loop
- Stale Closures: Ini terjadi kalau kamu lupa masukin suatu variabel (state, props, atau fungsi) yang dipakai di dalam
useEffect
ke dalam array dependencies. Akibatnya,useEffect
akan "melihat" nilai lama dari variabel tersebut, bukan nilai yang paling baru. ESLint denganeslint-plugin-react-hooks
punya aturanexhaustive-deps
yang sangat membantu untuk mendeteksi ini. Dengarkan peringatan ESLint! Hampir selalu benar.
javascript
const [count, setCount] = useState(0);
useEffect(() => {
// Ini salah kalau kamu cuma mau console.log pas mount.
// Kalau kamu lupa [count] di dependencies, maka kalau count berubah,
// console.log ini akan tetap menampilkan nilai count lama saat efek ini pertama kali dipanggil!
console.log('Nilai count lama:', count);
}, []); // <-- ERROR: ESLint akan bilang "React Hook useEffect has a missing dependency: 'count'."
Solusi: Masukkan count
ke dependencies: }, [count]);
- Infinite Loop: Ini bahaya! Terjadi kalau kamu update state di dalam
useEffect
, dan update state itu sendiri memicuuseEffect
untuk jalan lagi, dan seterusnya.
javascript
useEffect(() => {
// JANGAN LAKUKAN INI!
setCount(count + 1); // Ini akan memicu render ulang, lalu useEffect jalan lagi, dst.
}, [count]); // Karena count di dependencies, dia akan terus jalan!
Untuk menghindarinya: * Pastikan update state di dalam useEffect
punya kondisi yang jelas kapan harus dijalankan (misalnya, hanya jika data benar-benar berubah). * Gunakan useRef
untuk menyimpan nilai yang tidak ingin memicu useEffect
untuk jalan ulang. * Jika kamu hanya ingin mengubah state berdasarkan state sebelumnya, gunakan functional update dari setState
(misalnya setCount(prevCount => prevCount + 1)
).
2. Kapan TIDAK Memakai useEffect
?
useEffect
itu buat side effects. Jangan pakai useEffect
untuk hal-hal yang bukan side effect, misalnya:
- Menghitung nilai baru dari props atau state yang sudah ada: Kalau kamu cuma mau menghitung
fullName
darifirstName
danlastName
, cukup lakukan di dalam body komponen di luaruseEffect
.
javascript
// JANGAN LAKUKAN INI
// useEffect(() => {
// setFullName(${firstName} ${lastName});
// }, [firstName, lastName]);
- Melakukan validasi atau mengatur state secara langsung setelah render: Kalau kamu mau set state, tapi tidak ada efek samping yang terjadi, langsung saja di dalam body komponen.
javascript
// JANGAN LAKUKAN INI (kalau tujuannya cuma set default value)
// useEffect(() => {
// if (value === null) setValue(defaultValue);
// }, [value, defaultValue]);
3. Memecah useEffect
(Separate Concerns)
Kalau kamu punya banyak side effect yang tidak saling berkaitan, pecah saja menjadi beberapa useEffect
terpisah. Ini membuat kode lebih mudah dibaca, dipahami, dan di-debug.
jsx
function MyComplexComponent() {
const [userId, setUserId] = useState(1);
const [theme, setTheme] = useState('light');// Efek 1: Fetch user data (tergantung userId)
useEffect(() => {
console.log(Fetching user with ID: ${userId});
// ... fetch logic ...
}, [userId]);// Efek 2: Update document title (tergantung theme)
useEffect(() => {
document.title = Aplikasi - ${theme} mode;
}, [theme]);// Efek 3: Log setiap kali komponen di-render
useEffect(() => {
console.log('Komponen MyComplexComponent di-render.');
}); // Tanpa dependencies, jalan setiap renderreturn (
// ... UI ...
User ID: {userId}
setUserId(userId + 1)}>Next User
Theme: {theme}
setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme
);
}
Ini jauh lebih bersih daripada menyatukan semua logika di satu useEffect
yang besar.
4. useCallback
dan useMemo
dengan useEffect
(Optimasi Tingkat Lanjut)
Kadang, kamu mungkin perlu memasukkan fungsi atau objek ke dalam array dependencies useEffect
. Masalahnya, setiap kali komponen di-render ulang, fungsi atau objek yang didefinisikan di dalam komponen akan dibuat ulang (memiliki referensi memori yang berbeda). Ini bisa menyebabkan useEffect
jalan ulang padahal isinya sama.
Di sinilah useCallback
dan useMemo
berperan:
useCallback
: Digunakan untuk "memoize" fungsi. Artinya, fungsi yang dibuat hanya akan berubah referensinya jika dependencies dariuseCallback
itu sendiri berubah.useMemo
: Digunakan untuk "memoize" nilai atau objek. Artinya, nilai/objek yang dihasilkan hanya akan dihitung ulang jika dependencies dariuseMemo
itu sendiri berubah.
jsx
import React, { useEffect, useState, useCallback } from 'react';function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);// Fungsi fetchData ini akan dibuat ulang setiap kali SearchComponent di-render
// Tapi dengan useCallback, referensinya akan stabil kecuali query berubah
const fetchData = useCallback(async () => {
if (!query) {
setResults([]);
return;
}
console.log(Mencari data untuk: ${query});
// Simulasi API call
const response = await new Promise(resolve => setTimeout(() => {
resolve([Result for ${query} 1, Result for ${query} 2]);
}, 500));
setResults(response);
}, [query]); // fetchData hanya akan dibuat ulang jika 'query' berubahuseEffect(() => {
fetchData();
}, [fetchData]); // Karena fetchData di-memoize, useEffect hanya akan jalan jika query berubahreturn (
setQuery(e.target.value)}
placeholder="Cari..."
/>
{results.map((r, index) => (
{r}
))}
);
}
Dengan useCallback
, fetchData
akan memiliki referensi yang stabil selama query
tidak berubah. Ini membuat useEffect
di bawahnya hanya akan jalan jika fetchData
(yang bergantung pada query
) benar-benar berubah, atau dengan kata lain, ketika query
berubah. Ini penting untuk optimasi agar useEffect
tidak terlalu sering jalan.
useEffect
vs. Class Lifecycle Methods (Sedikit Perbandingan)
Buat kamu yang pernah ngoding React dengan class component, mungkin penasaran gimana useEffect
ini sama dengan lifecycle methods yang lama.
componentDidMount
: Mirip denganuseEffect(callback, [])
.componentDidUpdate
: Mirip denganuseEffect(callback, [dependencies])
atauuseEffect(callback)
(tanpa dependencies, tapi ini jarang dipakai).componentWillUnmount
: Mirip denganreturn cleanupFunction
dariuseEffect(callback, [])
.
Kelebihan useEffect
adalah dia menyatukan ketiga konsep ini menjadi satu API yang lebih konsisten dan fleksibel, dan juga memungkinkan kita untuk mengelompokkan logika side effect berdasarkan "apa yang dilakukan" bukan "kapan dilakukan".
Sedikit Tentang useLayoutEffect
Ada juga useLayoutEffect
, yang sangat mirip dengan useEffect
tapi ada satu perbedaan kunci:
useEffect
: Berjalan secara asynchronous* setelah browser selesai mengecat (paint) ulang DOM. Ini adalah yang paling umum dan disarankan. useLayoutEffect
: Berjalan secara synchronous setelah semua DOM mutations, tapi sebelum* browser mengecat ulang layar.
Kapan pakai useLayoutEffect
? Kalau kamu perlu melakukan sesuatu yang memanipulasi DOM dan perlu diukur (misalnya, mendapatkan lebar atau posisi elemen) sebelum browser menampilkan hasilnya ke user. Kalau kamu pakai useEffect
untuk ini, bisa jadi ada "flicker" atau kedipan di layar karena browser sudah sempat menggambar UI sebelum perubahanmu diaplikasikan. Tapi ini kasus yang jarang. Untuk sebagian besar kasus, useEffect
sudah cukup.
Kesimpulan
Gimana, sekarang sudah lebih paham kan sama useEffect
? Memang butuh sedikit latihan buat terbiasa dengan konsep dependencies dan fungsi cleanup-nya. Tapi begitu kamu menguasainya, useEffect
bakal jadi salah satu senjata paling ampuh di kotak perkakas React kamu.
Ingat kuncinya:
useEffect(callback)
(tanpa array): Jalan setiap render. Jarang dipakai.useEffect(callback, [])
(array kosong): Jalan sekali saat mount. Cocok buat inisialisasi.useEffect(callback, [dep1, dep2])
(array dengan dependencies): Jalan saat dependencies berubah. Paling sering dipakai.return cleanupFunction
: Penting banget buat membersihkan efek (timers, event listeners, subscriptions) agar tidak ada memory leak.- Perhatikan ESLint: Dia sahabat terbaikmu dalam menemukan missing dependencies.
Jangan takut untuk bereksperimen dan coba sendiri contoh-contoh di atas. Semakin sering kamu praktik, semakin natural kamu akan memahami kapan dan bagaimana menggunakan useEffect
dengan benar. Selamat ngoding, ya!