use adalah API React 19 yang membaca nilai dari promise atau context saat komponen dirender. use(promise) menjeda komponen sampai promise resolve, sambil menampilkan fallback dari <Suspense> terdekat; use(context) bekerja seperti useContext tapi boleh berada di dalam if atau perulangan.
Fungsi fetchUser di bawah menggantikan request sungguhan.
Render pertama menampilkan "Loading..." selama satu detik, lalu profilnya. Klik User 2: fallback kembali muncul selama promise baru masih pending. Profile tidak punya state loading sendiri; komponen ini ditulis seolah datanya sudah ada.
Cara kerja use(promise)
Saat Profile memanggil use(userPromise):
- Kalau promise sudah resolve,
usemengembalikan nilainya dan rendering berlanjut. - Kalau masih pending, React berhenti merender
Profiledan menampilkan fallback dari<Suspense>terdekat di atasnya. Saat promise selesai, React merenderProfilelagi, dan kali iniusemengembalikan nilainya. - Kalau promise reject, React menampilkan error boundary terdekat sebagai gantinya.
React mengingat hasilnya di object promise itu sendiri. Itulah sebabnya promise harus berupa object yang sama di render berikutnya.
Untuk tetap menampilkan profil lama di layar selama profil berikutnya dimuat, atur promise di dalam transition. React lalu menunggu alih-alih menampilkan fallback lagi:
<button onClick={() => startTransition(() => setUserPromise(fetchUser(2)))}>User 2</button>
Di mana membuat promise
Inilah bagian yang sering menjebak. Menulis pemanggilan fetch di dalam komponen yang membacanya terlihat wajar, tapi tidak berhasil:
// Do not do this
function Profile({ id }) {
const user = use(fetchUser(id)); // a new promise on every render
return <p>{user.name}</p>;
}
Setiap render memanggil fetchUser dan mendapat promise baru yang masih pending. Profile ditangguhkan. Saat promise itu resolve, React merender Profile lagi, yang membuat promise pending yang lain, yang menangguhkannya lagi. Komponen itu tidak pernah menampilkan datanya, dan tab network penuh dengan request.
Buat promise di tempat yang tidak dijalankan ulang di setiap render komponen pembacanya:
- Di parent, disimpan di state (seperti contoh pertama) atau dibuat di event handler.
- Di tingkat module, saat datanya tidak bergantung pada props:
const configPromise = fetchConfig();di atas komponen. - Di cache dengan argumen sebagai kuncinya, sehingga id yang sama mengembalikan promise yang sama.
- Di framework: Server Component bisa mengirim promise ke client component sebagai prop, dan loader route melakukan hal yang sama. Inilah kasus yang memang menjadi tujuan
usedirancang.
Berikut versi dengan cache, sehingga komponen bisa meminta data berdasarkan id dan tetap mendapat promise yang stabil:
Klik ketiga kota satu per satu, lalu kembali ke kota yang sudah pernah kamu buka. Console mencatat satu request per kota, dan kota yang sudah pernah kamu lihat muncul tanpa fallback, karena promise-nya sudah resolve. Hapus pengecekan if (!cache.has(city)) dan komponennya kembali ditangguhkan selamanya.
Cache sungguhan juga butuh cara untuk membuat entri kedaluwarsa. Library data dan framework menangani hal itu, itulah sebabnya sebagian besar aplikasi mendapatkan promise-nya dari sana.
use(context), bahkan di dalam if
use(SomeContext) mengembalikan nilai yang sama dengan useContext(SomeContext). Perbedaannya ada di tempat kamu bisa memanggilnya. Setiap hook lain harus berjalan di tingkat teratas, dengan urutan yang sama di setiap render (aturan hook). use boleh dipanggil setelah early return, di dalam kondisi, atau di dalam perulangan.
Klik tombolnya untuk berpindah di antara kedua cabang. Ganti use(UserContext) dengan useContext(UserContext) dan kodenya melanggar aturan hook, karena hook itu akan berjalan di sebagian render dan tidak di render lainnya.
use tetap punya batasan: panggil hanya saat merender komponen atau di dalam custom hook. Di event handler atau effect, baca context lewat nilai yang sudah kamu dapatkan saat render.
Error: error boundary, bukan try/catch
Saat promise reject, error-nya diteruskan ke error boundary terdekat, sama seperti error saat render.
Buka Post 2 dan boundary menampilkan pesan error-nya. Buka Post 1 lagi dan semuanya berjalan: key={id} memberi boundary state baru untuk setiap post, jadi boundary tidak terus menampilkan error lama.
Kamu tidak bisa menangkap use yang pending atau reject dengan try/catch. Saat promise masih pending, use menghentikan render dengan melempar nilai khusus, dan catch di sekitarnya akan menelan nilai itu. Nilai yang tertangkap adalah error yang pesannya diawali "Suspense Exception: This is not a real error!", dan di development React juga mencatat "use was called from inside a try/catch block". Untuk menampilkan nilai pengganti alih-alih layar error, tangani penolakan pada promise sebelum mengirimnya ke bawah:
const postPromise = fetchPost(id).catch(() => 'This post could not be loaded.');
use dibandingkan dengan fetch di useEffect
Pola client yang klasik mengambil data di effect dan menyimpan tiga state:
function Profile({ id }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
fetchUser(id).then(
(u) => !ignore && setUser(u),
(e) => !ignore && setError(e),
);
return () => {
ignore = true;
};
}, [id]);
if (error) return <p>Error</p>;
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
useEffect | use | |
|---|---|---|
| Kapan request dimulai | setelah render pertama tampil di layar | saat promise dibuat, mungkin sebelum komponen dirender |
| State loading | if (!user) milikmu sendiri | <Suspense> terdekat |
| Error | state error milikmu sendiri | error boundary terdekat |
| Race condition | kamu yang mencegahnya (ignore) | komponen selalu membaca promise yang diberikan kepadanya |
| Siapa yang membuat promise | komponen itu sendiri | parent, cache, atau framework |
Versi effect berdiri sendiri, itulah sebabnya cara ini masih menjadi pendekatan paling umum di aplikasi tanpa framework; halaman mengambil data membahasnya secara lengkap. use paling unggul saat sesuatu di atas komponen memulai request lebih awal dan mengirim promise-nya ke bawah.
Pertanyaan yang sering diajukan
Apa fungsi hook use di React?
use(resource) mengembalikan nilai dari promise atau context. Dengan promise, komponen ditangguhkan sampai promise itu resolve dan <Suspense> terdekat menampilkan fallback-nya; kalau promise reject, error boundary terdekat yang tampil.
Apakah use boleh dipanggil secara kondisional?
Boleh. use adalah satu-satunya hook yang bisa kamu panggil di dalam pernyataan if, perulangan, dan setelah early return. Hook ini tetap harus dipanggil dari komponen atau hook, tidak pernah dari event handler atau effect.
Mengapa use(fetch()) di dalam komponen saya berulang tanpa henti?
Setiap render membuat promise baru. Komponen ditangguhkan karenanya, React merender lagi saat promise itu resolve, dan render itu membuat promise pending yang lain lagi. Buat promise di luar render: di state milik parent, di tingkat module, di cache, atau di loader dari framework.
Bisakah use dibungkus dengan try/catch?
Tidak. Saat promise masih pending, use menghentikan render dengan melempar sesuatu, dan menangkapnya akan merusak mekanisme itu. Tangani promise yang reject dengan error boundary, atau pasang .catch() pada promise agar ia resolve ke nilai pengganti.
Apakah sebaiknya saya mengganti pengambilan data dengan useEffect menjadi use?
use butuh promise yang dibuat sekali lalu dipakai ulang, dan itulah yang diberikan framework dan library data. Di aplikasi client biasa, mengambil data di effect (atau dengan library seperti TanStack Query) masih umum dan tidak masalah.