Membedakan Event dengan Effect
Event handler hanya akan dijalankan ulang ketika kita melakukan interaksi yang sama kembali. Berbeda dengan event handler, Effect akan disinkronisasi ulang jika beberapa nilai yang dibaca,seperti prop atau variabel state, berbeda dari apa yang ada pada render sebelumnya. Terkadang Anda juga ingin mencampur perilaku keduanya: Sebuah Effect yang dijalankan ulang menanggapi beberapa nilai tertentu tapi tidak pada yang lainnya. Halaman ini akan mengajari Anda cara melakukannya.
Anda akan mempelajari
- Cara memilih antara event handler dan Effect
- Mengapa Effect bersifat reaktif, dan event handler tidak
- Bagaimana cara kita membuat beberapa bagian dari kode Effect agar tidak reaktif
- Apa yang dimaksud Effect Event, dan bagaimana untuk mengeluarkan dari Effect Anda
- Bagaimana cara membaca nilai props dan *state terbaru dari Effects menggunakan Effect Event
Memilih antara event handler atau Effect
Pertama, mari kita rangkum perbedaan antara event handler dan Effects.
Bayangkan Anda sedang menerapkan suatu komponen ruang obrolan (chatroom). Persyaratan Anda terlihat seperti ini:
- Komponen Anda harus terhubung secara otomatis ke ruang obrolan yang terpilih.
- Ketika Anda menekan tombol ‘Kirim, itu harus mengirimkan pesan ke dalam obrolan.
Katakanlah Anda telah mengimplementasikan kode tersebut, tapi Anda tidak yakin dimana meletakannya. Apakah Anda perlu menggunakan event handler atau Effects? Setiap kali Anda butuh jawaban dari pertanyaan ini, pertimbangkan mengapa kode tersebut perlu dijalankan.
Event handler tereksekusi karena interaksi tertentu
Dari sudut pandang pengguna, pengiriman pesan harus terjadi karena tombol “Kirim” tertentu diklik. Pengguna akan agak kesal jika kita mengirim pesan mereka di waktu lain atau karena alasan lain. Inilah sebabnya mengapa mengirim pesan harus menjadi event handler. Event handler memungkinkan kita menangani interaksi tertentu:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
function handleSendClick() {
sendMessage(message);
}
// ...
return (
<>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button onClick={handleSendClick}>Kirim</button>;
</>
);
}
Dengan event handler, kita bisa yakin bahwa sendMessage(message)
hanya akan tereksekusi jika pengguna menekan tombol.
Effect tereksekusi ketika sinkronisasi diperlukan
Jangan lupa bahwa kita juga harus menjaga agar komponen kita tetap terhubung dengan ruang obrolan. Kita perlu memikirkan ke mana kode tersebut seharusnya ditempatkan.
Kita harus menjalankan kode tersebut untuk memastikan komponen ini tetap terhubung ke server obrolan yang dipilih, bukan karena interaksi tertentu. Tidak peduli bagaimana atau mengapa pengguna berpindah ke layar ruang obrolan, yang penting adalah sekarang mereka melihatnya dan dapat berinteraksi dengannya. Oleh karena itu, kita perlu memastikan komponen kita tetap terhubung ke server obrolan yang dipilih, bahkan jika pengguna tidak berinteraksi dengan aplikasi kita sama sekali. Inilah sebab mengapa kita perlu menggunakan Effect untuk memastikan hal tersebut terjadi:
function ChatRoom({ roomId }) {
// ...
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
Dengan kode ini, kita dapat memastikan adanya koneksi aktif ke server obrolan yang dipilih saat ini, tanpa perlu bergantung pada interaksi pengguna. Tidak peduli apakah pengguna hanya membuka aplikasi kita, memilih ruangan yang berbeda, atau menavigasi ke layar lain kemudian kembali, Effect dapat memberikan jaminan bahwa komponen akan tetap disinkronisasi dengan ruangan obrolan yang dipilih saat ini. Sehingga, komponen akan selalu terhubung ke server obrolan yang dipilih saat ini dan akan tersambung kembali setiap kali diperlukan.
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); function handleSendClick() { sendMessage(message); } return ( <> <h1>Selamat datang di room {roomId}!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> <button onClick={handleSendClick}>Kirim</button> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); const [show, setShow] = useState(false); return ( <> <label> Pilih chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <button onClick={() => setShow(!show)}> {show ? 'Tutup chat' : 'Buka chat'} </button> {show && <hr />} {show && <ChatRoom roomId={roomId} />} </> ); }
Nilai reaktif dan logika reaktif
Secara intuitif, kita bisa mengatakan bahwa event handler selalu dipicu “secara manual”, misalnya dengan mengklik sebuah tombol. Sementara itu, Effect berjalan “secara otomatis”. Mereka berjalan dan berjalan kembali sesering yang diperlukan untuk memastikan sinkronisasi tetap terjaga.
Namun, ada cara yang lebih tepat untuk memikirkan perbedaan antara keduanya.
Props, state, dan variabel yang dideklarasikan di dalam komponen disebut nilai reaktif. Dalam contoh ini, serverUrl
bukan merupakan nilai reaktif, melainkan roomId
dan message
. Keduanya berpartisipasi dalam aliran data rendering, sehingga harus diatur sebagai nilai reaktif agar sinkronisasi dapat terjaga:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
}
Nilai reaktif seperti ini dapat berubah karena rendering ulang suatu komponen. Misalnya, pengguna dapat melakukan beberapa tindakan, seperti mengedit message
atau memilih roomId
yang berbeda di menu drop-down. Event handler dan Effect merespon perubahan tersebut dengan cara yang berbeda:
- Kode di dalam event handler bersifat non-reaktif. Ketika sebuah event handler dijalankan (yang disebabkan oleh tindakan pengguna seperti tombol diklik), mereka membaca nilai reaktif tanpa bereaksi terhadap perubahannya. Artinya, jika kita ingin event handler membaca suatu nilai reaktif, mereka tidak akan merespon ketika nilainya berubah kecuali tindakan pengguna yang sama kembali dijalankan.
- Kode di dalam Effect bersifat reaktif. Jika kita menggunakan Effect untuk membaca nilai reaktif, kita harus mendeklarasikannya sebagai salah satu dependensi Effect tersebut. Kemudian jika render ulang menyebabkan nilai tersebut berubah, React akan menjalankan kembali logika Effect dengan nilai yang baru, sehingga memastikan sinkronisasi data terjaga.
Mari kita lihat kembali contoh sebelumnya untuk mengilustrasikan perbedaan ini.
Kode di dalam event handler bersifat tidak reaktif
Mari kita lihat baris kode ini. Apakah kode ini seharusnya merupakan nilai reaktif atau tidak?
// ...
sendMessage(message);
// ...
Dari sudut pandang pengguna, perubahan dalam nilai message
tidak selalu berarti mereka hendak mengirim pesan. Hal ini mungkin hanya berarti bahwa pengguna sedang mengetik. Oleh karena itu, logika pengiriman pesan tidak seharusnya diatur sebagai nilai reaktif, agar tidak dipicu secara otomatis setiap kali nilai message berubah. Sebaliknya, logika ini sebaiknya diimplementasikan pada event handler:
function handleSendClick() {
sendMessage(message);
}
Event handler bersifat tidak reaktif, jadi sendMessage(message)
hanya akan tereksekusi saat pengguna mengklik tombol Kirim.
Kode didalam Effect bersifat reaktif
Sekarang mari kita kembali ke baris ini:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
// ...
Dari sudut pandang pengguna, perubahan pada roomId
berarti mereka ingin terhubung ke ruangan yang berbeda. Dengan kata lain, logika untuk menghubungkan ke chatroom harus reaktif. Kita ingin baris kode ini “mengikuti” nilai reaktif, dan berjalan lagi jika nilai tersebut berubah. Itu sebabnya kita implementasikan sebagai Effect:
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId]);
Effect bersifat reaktif, jadi createConnection(serverUrl, roomId)
dan connection.connect()
akan terksekusi setiap kali nilai roomId
berubah. Effect ini membantu kita menjaga koneksi tetap tersinkronasi sesuai chatroom yang dipilih saat ini.
Mengekstrak logika non-reaktif dari Effect
Semuanya menjadi lebih kompleks ketika kita ingin menggabungkan logika reaktif dengan logika non-reaktif.
Misalnya, pertimbangkan skenario di mana kita ingin menampilkan notifikasi saat pengguna terhubung ke obrolan. Serta kita juga ingin membaca nilai tema saat ini (terang atau gelap) dari props dari komponen, sehingga notifikasi yang ditampilkan akan memiliki warna yang tepat sesuai dengan tema yang digunakan:
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Terhubung!', theme);
});
connection.connect();
// ...
Namun, theme
adalah nilai reaktif (dapat berubah sebagai hasil dari render ulang), dan setiap nilai reaktif yang dibaca oleh Effect harus dideklarasikan sebagai dependensi Effect tersebut. Sekarang kita harus menentukan theme
sebagai dependensi Effect:
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Terhubung!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ Semua dependensi telah didelarasikan
// ...
Cobalah contoh di bawah ini dan cari tahu apakah kamu bisa menemukan masalah pada program berikut:
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Terhubung!', theme); }); connection.connect(); return () => connection.disconnect(); }, [roomId, theme]); return <h1>Selamat datang di room {roomId}!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Pilih chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Pakai dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Ketika roomId
berubah, chat terhubung kembali seperti yang diharapkan. Tetapi karena theme
juga termasuk dependensi Effect, chat juga terhubung kembali setiap kita beralih antara tema gelap dan terang. Itu tidak bagus!
Dengan kata lain, kita tidak ingin baris ini menjadi reaktif, meskipun berada di dalam Effect (yang reaktif):
// ...
showNotification('Connected!', theme);
// ...
Kita memerlukan cara untuk memisahkan logika non-reaktif ini dari logika Effect reaktif di sekitarnya.
Mendeklarasikan Effect Event
Gunakan Hook khusus useEffectEvent
untuk mengekstrak logika non-reaktif ini dari Effect:
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Terhubung!', theme);
});
// ...
Disini, onConnected
disebut dengan Effect Event. Meskipun merupakan bagian dari logika Effect, namun mempunyai sifat seperti event handler. Logika di dalamnya tidak reaktif, dan selalu memperhatikan nilai terbaru dari props dan state.
Sekarang kita dapat memanggil Effect Event onConnected
dari dalam Effect:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Terhubung!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Semua dependensi dideklarasikan, karena theme tidak lagi merupakan dependensi Effect, maka tidak akan tereksekusi ulang jika nilai theme berubah.
// ...
Masalah terpecahkan. Perhatikan bahwa kita harus menghapus onConnected
dari daftar dependensi Effect. Effect Event tidak reaktif dan harus dihilangkan dari dependensi.
Pastikan bahwa program baru berfungsi seperti yang kita harapkan:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Terhubung!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Selamat datang di room {roomId}!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Pilih chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Gunakan dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Kita dapat menganggap Effect Event sangat mirip dengan event handler. Perbedaan utamanya adalah event handler dijalankan sebagai respons terhadap interaksi pengguna, sedangkan Effect Event dipicu oleh Anda dari Effect. Effect Event memungkinkan kita “memutus rantai” antara reaktivitas Effect dan kode yang seharusnya tidak reaktif.
Membaca props dan state terbaru dengan Effect Event
Effect Event memungkinkan kita memperbaiki banyak pola di mana kamu mungkin tergoda untuk menonaktifkan warning dari dependency linter.
Misalnya, kita memiliki Effect untuk mencatat log kunjungan halaman:
function Page() {
useEffect(() => {
logVisit();
}, []);
// ...
}
Kemudian, kita menambahkan beberapa rute ke situs tersebut. Sekarang komponen Page
kita menerima prop url
dengan path saat ini. Kita ingin pass url
ke parameter function logVisit
, tetapi dependency linter pasti akan mengeluarkan warning:
function Page({ url }) {
useEffect(() => {
logVisit(url);
}, []); // 🔴 React Hook useEffect has a missing dependency: 'url'
// ...
}
Sekarang mari pikirkan apa tujuan awal dari kode ini. Kita ingin mencatat setiap kunjungan terpisah untuk tiap URL karena setiap URL merepresentasikan halaman yang berbeda. Artinya, pemanggilan logVisit
ini seharusnya berperilaku reaktif terhadap url
. Oleh karena itu, dalam kasus ini, akan lebih baik jika kita mengikuti dependency linter dan menambahkan url
sebagai salah satu dependensi Effect.
function Page({ url }) {
useEffect(() => {
logVisit(url);
}, [url]); // ✅ Semua dependensi telah dideklarasikan
// ...
}
Sekarang katakanlah kita ingin memasukkan jumlah barang di keranjang belanja bersama dengan setiap kunjungan halaman:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems'
// ...
}
Kita menggunakan numberOfItems
di dalam Effect, sehingga linter meminta agar kita menambahkannya sebagai salah satu dependensi. Namun, sebenarnya kita tidak ingin memanggil logVisit
secara reaktif terhadap numberOfItems
. Jika pengguna menambahkan barang ke dalam keranjang belanja dan numberOfItems
berubah, ini tidak berarti bahwa pengguna telah mengunjungi kembali halaman tersebut. Dalam artian lain, melakukan kunjungan ke halaman adalah suatu “peristiwa (event)” yang terjadi pada saat tertentu.
Sebaiknya, pisahkan kode tersebut menjadi dua bagian:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onVisit(url);
}, [url]); // ✅ Semua dependensi telah dideklarasikan
// ...
}
Di sini, onVisit
adalah suatu Effect Event. Kode di dalamnya tidak bersifat reaktif. Oleh karena itu, kita dapat menggunakan numberOfItems
(atau reactive value lain!) tanpa khawatir mengakibatkan kode di sekitarnya dijalankan ulang saat terjadi perubahan.
Namun di sisi lain, Effect itu sendiri tetap bersifat reaktif. Kode di dalam Effect menggunakan prop url
, sehingga Effect tersebut akan dijalankan ulang setelah setiap render dengan url
yang berbeda. Hal ini pada akhirnya akan memanggil Effect Event onVisit
.
Akibatnya, logVisit
akan terpanggil untuk setiap perubahan pada url
, dan selalu membaca numberOfItems
yang terbaru. Namun jika hanya nilai numberOfItems
yang berubah, hal ini tidak akan menyebabkan kode berjalan ulang.
Pendalaman
Di dalam basis kode yang sudah ada, terkadang kamu akan melihat aturan lint dinonaktifkan seperti ini:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
// 🔴 Avoid suppressing the linter like this:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
// ...
}
Setelah useEffectEvent
menjadi bagian versi stabil dari React, kami merekomendasikan untuk tidak menonaktifkan linter.
Kekurangan pertama dari menonaktifkan aturan tersebut adalah bahwa React tidak akan memberikan peringatan lagi ketika Effect yang kita buat perlu “bereaksi” terhadap dependensi reaktif baru yang kita tambahkan ke dalam kode. Pada contoh sebelumnya, kita menambahkan url
sebagai dependensi karena React mengingatkannya. Jika kita menonaktifkan linter, secara otomatis tidak akan ada lagi pengingat yang sama untuk perubahan Effect tersebut ke depannya. Hal ini dapat menyebabkan terjadinya bug.
Berikut ini contoh dari bug yang membingungkan yang terjadi karena penonaktifan linter. Pada contoh ini, fungsi handleMove
seharusnya membaca nilai variabel state canMove
yang terbaru untuk menentukan apakah titik harus mengikuti kursor atau tidak. Namun, canMove
selalu bernilai true
di dalam handleMove
.
Apakah kamu dapat menemukan penyebabnya?
import { useState, useEffect } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [canMove, setCanMove] = useState(true); function handleMove(e) { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } } useEffect(() => { window.addEventListener('pointermove', handleMove); return () => window.removeEventListener('pointermove', handleMove); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> <label> <input type="checkbox" checked={canMove} onChange={e => setCanMove(e.target.checked)} /> Titik bisa bergerak </label> <hr /> <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: 0.6, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> </> ); }
Masalah pada kode tersebut terletak pada penonaktifan lint dependency. Jika kita hapus penonaktifannya, maka kita akan melihat bahwa Effect tersebut harus membutuhkan fungsi handleMove
sebagai dependensi. Hal ini masuk akal, karena handleMove
dideklarasikan di dalam badan komponen, yang membuatnya menjadi sebuah nilai reaktif. Setiap nilai reaktif harus dijadikan dependensi, jika tidak, maka nilai tersebut berpotensi menjadi usang dari waktu ke waktu!
Penulis kode tersebut “membohongi” React dengan mengatakan bahwa Effect tersebut tidak memiliki dependensi ([]
) pada nilai yang reaktif. Inilah yang menyebabkan React tidak mensinkronisasikan kembali Effect tersebut setelah terjadinya perubahan pada canMove
(dan handleMove
). Karena React tidak mensinkronisasikan kembali Effect tersebut, maka handleMove
yang digunakan sebagai listener adalah fungsi handleMove
yang dibuat selama render awal. Selama render awal, canMove
bernilai true
, itulah sebabnya fungsi handleMove
dari render awal akan selalu melihat nilai tersebut.
Dengan tidak pernah menonaktifkan linter dependency, kita tidak akan pernah mengalami masalah dengan nilai yang usang.
Dengan useEffectEvent
, tidak perlu “berbohong” pada linter, dan kode dapat bekerja sesuai dengan yang kita harapkan:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [canMove, setCanMove] = useState(true); const onMove = useEffectEvent(e => { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } }); useEffect(() => { window.addEventListener('pointermove', onMove); return () => window.removeEventListener('pointermove', onMove); }, []); return ( <> <label> <input type="checkbox" checked={canMove} onChange={e => setCanMove(e.target.checked)} /> Titik bisa bergerak </label> <hr /> <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: 0.6, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> </> ); }
Hal ini tidak berarti bahwa useEffectEvent
selalu menjadi solusi yang tepat. Kita hanya perlu menerapkannya pada baris kode yang tidak ingin bersifat reaktif. Di dalam sandbox di atas, kita tidak ingin kode Effect bersifat reaktif terhadap canMove
. Itulah sebabnya masuk akal untuk mengekstrak ke Effect Event.
Baca Menghapus dependensi Effect untuk mengetahui alternatif lain yang tepat selain menonaktifkan linter.
Keterbatasan Effect Event
Effect Event memiliki keterbatasan sebagai berikut:
- Kita hanya bisa memanggilnya dari dalam Effect.
- Kita tidak boleh pass Effect Event (sebagai argumen) ke komponen atau Hook lain.
Sebagai contoh, jangan menggunakan Effect Event seperti ini:
function Timer() {
const [count, setCount] = useState(0);
const onTick = useEffectEvent(() => {
setCount(count + 1);
});
useTimer(onTick, 1000); // 🔴 Hindari: Pass Effect Event
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // Harus mendeklarasikan "callback" pada dependensi
}
Sebaliknya, selalu deklarasikan Effect Event di dekat Effect yang akan menggunakannya:
function Timer() {
const [count, setCount] = useState(0);
useTimer(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
const onTick = useEffectEvent(() => {
callback();
});
useEffect(() => {
const id = setInterval(() => {
onTick(); // ✅ Bagus: Hanya dipanggil di dalam Effect
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // Tidak perlu mendeklarasikan "onTick" (Effect Event) sebagai dependensi
}
Effect Event merupakan “bagian” yang tidak reaktif dari kode Effect. Effect Event harus dideklarasikan di dekat Effect yang menggunakannya.
Rekap
- Event handler berjalan sebagai respons terhadap interaksi tertentu.
- Effect berjalan ketika sinkronisasi diperlukan.
- Logika di dalam event handler tidak bersifat reaktif.
- Logika di dalam Effect bersifat reaktif.
- Kita dapat memindahkan logika yang tidak bersifat reaktif dari Effect ke dalam Effect Event.
- Hanya panggil Effect Event dari dalam Effect.
- Jangan pass Effect Event (sebagai argumen) ke dalam komponen atau Hook lain.
Tantangan 1 dari 4: Memperbaiki variabel yang tidak terupdate
Komponen Timer
ini menyimpan variabel state count
yang bertambah setiap satu detik. Nilai penambahan disimpan di dalam variabel state increment
. Kamu dapat mengontrol variabel increment
dengan tombol plus dan minus.
Namun, tidak peduli berapa kali kamu menekan tombol plus, nilai count
selalu bertambah satu setiap satu detik. Apa yang salah dengan kode ini? Mengapa increment
selalu sama dengan 1
di dalam kode Effect? Cari kesalahan tersebut dan perbaiki.
import { useState, useEffect } from 'react'; export default function Timer() { const [count, setCount] = useState(0); const [increment, setIncrement] = useState(1); useEffect(() => { const id = setInterval(() => { setCount(c => c + increment); }, 1000); return () => { clearInterval(id); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> <h1> Counter: {count} <button onClick={() => setCount(0)}>Reset</button> </h1> <hr /> <p> Setiap detik, nilai bertambah sebanyak: <button disabled={increment === 0} onClick={() => { setIncrement(i => i - 1); }}>–</button> <b>{increment}</b> <button onClick={() => { setIncrement(i => i + 1); }}>+</button> </p> </> ); }