useOptimistic adalah hook React 19 yang membuat layar bisa menampilkan hasil sebuah action sebelum action itu selesai. Kamu mengatur nilai optimistis di dalam action; React menampilkannya selama action pending, lalu kembali ke state asli. Kalau request-nya gagal, state asli tidak pernah berubah, jadi layar melakukan rollback dengan sendirinya.
API palsu di contoh ini menggantikan request sungguhan dan gagal di setiap pemanggilan ketiga, jadi kamu bisa melihat kedua hasilnya.
Klik tombolnya tiga kali, dengan jeda satu detik di antara setiap klik. Dua klik pertama langsung berganti dan baris yang tersimpan menyusul sedetik kemudian. Klik ketiga juga langsung berganti, lalu kembali saat request-nya gagal. Ganti calls % 3 menjadi calls % 2 dan setiap klik kedua akan gagal.
Sintaksnya
const [optimisticState, setOptimistic] = useOptimistic(state, updateFn?);
stateadalah nilai asli, biasanya dariuseState, props, atauuseActionState. Saat tidak ada action yang pending,optimisticStatepersis sama dengan nilai ini.setOptimistic(value)mengatur nilai optimistis selama action saat ini berjalan.updateFnbersifat opsional:(currentState, optimisticValue) => nextState. Dengan fungsi ini,setOptimisticmenerima perubahan alih-alih nilai baru yang utuh. Lihat bentuk reducer di bawah.
Siklus hidup satu klik selalu sama:
- Di dalam action, kamu memanggil
setOptimistic. Layar langsung diperbarui. - Action menunggu (await) request-nya.
- Kalau berhasil, kamu memperbarui state asli (di dalam
startTransitionsaat terjadi setelahawait). - Action selesai.
optimisticStatekembali mengikutistate: nilai yang tersimpan kalau berhasil, nilai lama kalau gagal.
Langkah 4 adalah alasan tidak ada kode rollback di contoh itu. Blok catch hanya mengatur pesan error.
Harus berjalan di dalam action
Nilai optimistis hanya ada selama sebuah transition masih pending, jadi setOptimistic harus dipanggil di dalamnya. Semua ini dihitung:
- fungsi yang kamu berikan ke
startTransition(ataustartTransitiondariuseTransition), - fungsi yang diberikan ke
<form action={...}>atau<button formAction={...}>, - action yang kamu berikan ke useActionState.
Kalau dipanggil dari onClick biasa, nilai optimistis tidak punya action untuk ditempati. React langsung mengembalikannya, dan di development React mencatat "An optimistic state update occurred outside a transition or action".
Pembaruan state asli yang terjadi setelah await sebaiknya dibungkus dalam startTransition miliknya sendiri, seperti startTransition(() => setLiked(next)) di atas. Setelah await, React tidak bisa tahu bahwa kamu masih berada di dalam transition sebelumnya.
Daftar pesan dengan bentuk reducer
Saat state optimistisnya berupa daftar, berikan fungsi pembaruan sebagai argumen kedua. Setter lalu menerima item baru, dan React menambahkannya ke daftar asli apa pun isinya saat itu.
Kirim tiga pesan dengan teks yang berbeda, satu per satu. Setiap pesan muncul pudar dengan "(sending...)" dan menjadi solid setelah terkirim; pesan ketiga hilang dari chat dan muncul sebagai "Not sent". Kirim ketiganya dengan cepat dan semuanya tetap pudar sampai request terakhir selesai, karena React mempertahankan nilai optimistis sampai setiap action yang pending selesai. Form action sudah merupakan transition, jadi addOptimistic di sini tidak butuh startTransition sendiri.
Beri setiap item optimistis sebuah key yang tidak akan bentrok dengan item asli. Contoh ini memakai pending- ditambah teksnya, jadi mengirim teks yang sama dua kali selagi keduanya pending akan menimbulkan bentrokan; aplikasi sungguhan akan membuat id di client.
Beberapa pembaruan sekaligus
Karena fungsi pembaruan menerima state saat ini, pembaruan optimistis bisa bertumpuk. Klik dengan cepat dan setiap klik yang pending diterapkan di atas klik sebelumnya.
Klik empat kali dengan cepat. Keranjang langsung menampilkan 4. Saat semua request selesai, jumlahnya berhenti di 3, karena request ketiga gagal dan console memberitahukannya. React mempertahankan nilai optimistis sampai setiap action yang pending selesai, lalu menampilkan jumlah yang sebenarnya.
Kapan memakainya, dan kapan tidak
Pembaruan optimistis cocok untuk action yang hampir selalu berhasil dan mudah dibatalkan: like, bintang, toggle, mengganti nama, menambahkan pesan. Untuk kasus yang umum, pengguna tidak melihat spinner sama sekali.
Hindari di tempat yang "selesai" palsunya bisa menyesatkan: pembayaran, menghapus akun, apa pun yang mungkin ditindaklanjuti pengguna sebelum hasilnya diketahui. Untuk hal-hal seperti itu, tampilkan state pending dengan isPending dari useActionState atau useTransition dan tunggu jawaban yang sebenarnya.
Selalu beri tahu pengguna saat rollback terjadi. Nilai yang diam-diam berubah kembali terlihat seperti bug. Kedua contoh di atas menyimpan pesan error di state biasa agar pesan itu bertahan lebih lama dari action-nya.
Bersama useActionState
useOptimistic dan useActionState cocok dipakai bersama. Action dari useActionState sudah merupakan transition, jadi kamu bisa mengatur nilai optimistis di awal action itu, dan state yang dikembalikan adalah nilai asli yang menjadi tempat kembali nilai optimistis.
const [state, formAction] = useActionState(async (previous, formData) => {
const title = formData.get('title');
setOptimisticTitle(title);
const saved = await saveTitle(title);
return { title: saved.title };
}, { title: 'Untitled' });
const [optimisticTitle, setOptimisticTitle] = useOptimistic(state.title);
Selama penyimpanan berjalan, halaman menampilkan judul baru. Saat selesai, state.title berisi nilai yang tersimpan. Kalau saveTitle melempar error, error dari action itu diteruskan ke error boundary terdekat; untuk menampilkan judul lama beserta pesan, tangkap error-nya di dalam action dan kembalikan state sebelumnya ditambah error.
Kesalahan umum
Memanggil setter di luar action. Nilai optimistis tidak bertahan di layar, dan React memberi peringatan di development. Bungkus dengan startTransition atau pindahkan ke form action.
Lupa memperbarui state asli. Saat berhasil, action harus mengubah state yang dicerminkan useOptimistic (setLiked, setMessages). Kalau tidak, nilai optimistis hilang saat action selesai, dan request yang berhasil terlihat seperti rollback.
Memperbarui state asli setelah await tanpa transition. Pembaruannya tetap terjadi, tapi React mungkin menampilkannya di saat yang berbeda dari akhir action. Bungkus dengan startTransition, seperti yang dilakukan ketiga contoh.
Memakai nilai optimistis sebagai sumber kebenaran. Kirim request dan hitung total dari state asli. Nilai optimistis hanya untuk tampilan dan bisa dibuang kapan saja.
Pertanyaan yang sering diajukan
Apa fungsi useOptimistic?
useOptimistic memberimu salinan sebuah state yang bisa langsung kamu ubah selagi action async berjalan. Saat action selesai, salinan itu kembali mengikuti state asli, yang saat itu berisi hasil yang tersimpan atau nilai lamanya.
Bagaimana useOptimistic melakukan rollback saat terjadi error?
Hook ini tidak butuh jalur error khusus. Nilai optimistis hanya hidup selama action masih pending. Kalau request gagal dan kamu tidak pernah memperbarui state asli, hook ini kembali menampilkan state asli, yaitu nilai dari sebelum klik.
Mengapa saya mendapat "An optimistic state update occurred outside a transition or action"?
Setter-nya dipanggil dari event handler biasa. Panggil di dalam fungsi yang diberikan ke startTransition, di dalam <form action>, atau di dalam action dari useActionState.
Apa argumen kedua dari useOptimistic?
Fungsi pembaruan opsional, (currentState, optimisticValue) => newState, seperti reducer. Dengan fungsi itu, setter hanya menerima perubahannya (pesan baru, +1) dan React menghitung state optimistisnya, bahkan saat beberapa pembaruan sedang pending sekaligus.
Apakah useOptimistic hanya untuk formulir?
Tidak. Hook ini berfungsi di transition mana pun, jadi tombol yang memanggil startTransition(async () => { ... }) bisa memakainya sama seperti form action.