Skip to main content
BlogWeb Dev

React 19 use() dan Actions: Model Async yang Baru

Tur praktis fitur React 19: use() dan Actions, serta bagaimana useActionState dan useOptimistic menggantikan boilerplate state loading, error, dan pending secara manual.

Matthews Wong

Software Engineer

Juli 202610 menit baca
React 19 use() dan Actions: Model Async yang Baru

Foto oleh Christiaan Colen on flickr

React 19 secara diam-diam menulis ulang aturan tentang bagaimana komponen berkomunikasi dengan jaringan. Selama bertahun-tahun, mengambil data dan mengirim form di React selalu memiliki ritual yang sama: satu useState untuk hasil, satu useState untuk status loading, satu useState untuk error, lalu useEffect atau handler klik untuk menyambungkan semuanya. React 19 memadatkan sebagian besar boilerplate itu menjadi dua gagasan, yaitu fungsi use dan Actions, ditambah beberapa hook kecil yang dibangun di sekitarnya.

Panduan ini membahas apa yang sebenarnya dilakukan use, bagaimana Actions mengubah penanganan form dan tombol, serta di mana posisi useActionState dan useOptimistic. Tujuannya bukan menghafal seluruh permukaan API, melainkan memahami model mentalnya dengan cukup baik sehingga Anda tahu alat mana yang harus dipakai saat membangun fitur berbasis data berikutnya, baik itu form profil, kotak komentar, maupun daftar berpaginasi.

Apa yang Sebenarnya Dilakukan use()

Meskipun namanya terdengar seperti Hook, use bukanlah Hook dalam arti ketat, dan justru itulah yang membuatnya kuat. Fungsi ini bisa dipanggil di dalam kondisional, loop, dan setelah early return, sesuatu yang biasanya dilarang oleh Rules of Hooks. Apa yang dilakukan use mudah dijelaskan namun mudah pula disalahgunakan: berikan sebuah Promise atau objek Context, dan use mengembalikan nilai yang sudah resolve, sambil membuat komponen suspend selama Promise masih pending. Jika promise ditolak, error boundary terdekat akan menangkapnya, sehingga Anda tidak perlu lagi try dan catch di setiap fetch.

function Albums({ albumsPromise }) {
  // suspends until albumsPromise resolves
  const albums = use(albumsPromise);

  return (
    <ul>
      {albums.map((album) => (
        <li key={album.id}>{album.title}</li>
      ))}
    </ul>
  );
}

function AlbumsPage({ albumsPromise }) {
  return (
    <Suspense fallback={<Spinner />}>
      <Albums albumsPromise={albumsPromise} />
    </Suspense>
  );
}

Jangan pernah membuat Promise yang diberikan ke use langsung di dalam body komponen pada setiap render. Promise baru di setiap render akan merusak Suspense dan bisa memicu render berulang tanpa henti. Buat Promise sekali saja, di komponen induk, route loader, atau cache kecil berdasarkan URL, lalu turunkan instance Promise yang sama sebagai prop.

Suspense, Bukan useEffect, yang Mengelola Status Loading

Perubahan praktis dengan use terletak pada di mana status loading berada. Alih-alih komponen melacak isLoading miliknya sendiri, komponen induk yang memulai fetch dan menurunkan Promise, sementara Suspense boundary yang membungkus konsumer menampilkan fallback sampai data resolve. Ini mencerminkan cara framework server seperti Next.js melakukan streaming data, dan artinya model mental yang sama kini berlaku baik untuk client component yang membaca Context secara kondisional maupun untuk Server Component yang melakukan streaming Promise langsung ke client. Suspense boundary yang bertumpuk juga memungkinkan sebuah halaman di-streaming secara bertahap, menampilkan kerangka halaman segera sementara bagian yang lebih lambat, seperti daftar komentar atau panel rekomendasi, resolve secara independen tanpa menghambat tampilan lainnya.

Membaca Context Tanpa Batasan Lama

Karena use bisa berjalan di dalam statement if, Anda bisa membaca Context seperti tema atau locale hanya ketika cabang render tertentu memang membutuhkannya, bukan memanggil useContext tanpa kondisi di bagian atas fungsi dan berharap nilainya tidak diperlukan pada cabang lain. Ini perubahan kecil, namun menghilangkan sekumpulan solusi akal-akalan di mana developer dulu memecah satu komponen menjadi dua hanya untuk memenuhi Rules of Hooks lama terkait pembacaan Context secara kondisional.

Jika sebuah fetch tidak pernah berubah selama umur komponen, simpan Promise tersebut di sebuah Map berdasarkan parameter request. Satu baris caching itu biasanya menjadi pembeda antara use yang bekerja mulus dan use yang menyebabkan suspend berulang.

Actions Mengubah Fungsi Async Menjadi Primitif UI Kelas Utama

Sebuah Action di React 19 hanyalah fungsi async yang diberikan ke prop action pada form, prop formAction pada tombol, atau dibungkus dengan startTransition. Yang membuatnya istimewa adalah apa yang dilakukan React secara otomatis di sekitarnya: status pending dilacak tanpa useState tambahan, nilai form sebelumnya tetap terjaga jika action melempar error, dan error dapat ditangkap oleh error boundary. useActionState mengemas pola ini menjadi satu hook yang mengembalikan state saat ini, action yang sudah dibungkus untuk dipanggil, dan flag isPending, sehingga berperilaku seperti useReducer untuk efek samping yang dipicu oleh interaksi pengguna. Argumen permalink yang opsional juga penting untuk progressive enhancement: jika JavaScript belum selesai dimuat namun pengguna tetap mengirim form, browser masih dapat menavigasi ke URL tersebut sebagai submit form HTML biasa alih-alih gagal secara diam-diam.

async function updateNameAction(previousState, formData) {
  const name = formData.get("name");
  const error = await updateName(name);
  if (error) {
    return error;
  }
  redirect("/profile");
  return null;
}

function EditProfile() {
  const [error, submitAction, isPending] = useActionState(
    updateNameAction,
    null
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>
        {isPending ? "Saving..." : "Update"}
      </button>
      {error && <p>{error}</p>}
    </form>
  );
}

useOptimistic: Feedback Instan yang Kembali Otomatis Jika Gagal

UI optimistik dulunya membutuhkan pencatatan manual yang teliti: perbarui state lokal segera, simpan nilai sebelumnya, lalu kembalikan jika request gagal. useOptimistic memformalkan pola ini. Hook ini menerima nilai terkonfirmasi saat ini dan mengembalikan nilai optimistik beserta setter-nya. Panggil setter secara sinkron di dalam sebuah Action sebelum request yang di-await selesai, dan React langsung menampilkan nilai optimistik tersebut, lalu otomatis kembali ke state asli begitu Action selesai, baik berhasil maupun melempar error.

function ChangeName({ currentName, onUpdateName }) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName);

  const submitAction = async (formData) => {
    const newName = formData.get("name");
    setOptimisticName(newName);
    const updatedName = await updateName(newName);
    onUpdateName(updatedName);
  };

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <input type="text" name="name" disabled={currentName !== optimisticName} />
    </form>
  );
}

useOptimistic hanya kembali secara otomatis ketika Action yang memicunya benar-benar selesai atau melempar error. Jika Anda memanggil setter di luar sebuah Action, atau jika fungsi async di sekitarnya tidak pernah resolve karena promise yang tidak ditangani, nilai optimistik bisa terlihat tertahan di layar tanpa batas waktu.

Memilih Hook yang Tepat untuk Setiap Kebutuhan

Dengan empat API yang saling berkaitan kini tersedia, akan lebih membantu jika membandingkannya secara berdampingan daripada memilih dari memori. Tabel di bawah ini mencantumkan tujuan praktis masing-masing serta situasi ketika pilihan itu jelas lebih tepat dibanding alternatif lainnya. Perlu dicatat bahwa useFormStatus sengaja bukan pengganti useActionState; hook ini ada khusus agar tombol submit yang berada jauh di dalam tree komponen bisa membaca status pending dari form induknya tanpa status tersebut harus diturunkan sebagai prop.

APITujuanGunakan saat
use()Membongkar Promise atau membaca Context saat render, suspend sampai siapMembaca data yang diturunkan sebagai Promise yang sudah di-cache, atau membaca Context secara kondisional
useActionStateMembungkus Action async dan melacak hasil, error, serta flag pending-nyaMenangani submit form atau klik tombol yang memanggil mutasi ke server
useOptimisticMenampilkan update UI instan yang otomatis kembali jika Action gagalLike, follow, pesan chat, atau interaksi apa pun di mana feedback instan lebih penting daripada kepastian
useFormStatusMembaca status pending dari form induk terdekat dari komponen anakTombol submit atau spinner berada di komponen yang terpisah dari form itu sendiri

Daftar Periksa Migrasi Praktis

Mengadopsi API ini paling baik dilakukan sebagai migrasi bertahap, bukan penulisan ulang total. Mulailah dari form dan tombol yang sudah memiliki state pending dan error manual paling banyak, karena di situlah pengurangan boilerplate paling terasa dan risikonya paling kecil. Tidak ada keharusan untuk menyentuh data fetching dan penanganan form dalam satu langkah yang sama; memperlakukannya sebagai dua migrasi terpisah membuat setiap pull request cukup kecil untuk direview dengan teliti dan bisa di-rollback secara independen jika terjadi regresi.

  • Ganti useState isSubmitting manual beserta blok try catch di sekitar handler form dengan useActionState terlebih dahulu, karena ini perubahan yang paling aman dan paling terisolasi.
  • Pindahkan panggilan fetch yang saat ini berada di dalam useEffect dengan flag loading menjadi pembacaan Promise yang di-cache melalui use, dibungkus dalam Suspense boundary dengan fallback yang sungguhan, bukan layar kosong.
  • Tambahkan useOptimistic hanya pada interaksi di mana pengguna mengharapkan feedback instan, seperti menandai favorit atau mengirim komentar, bukan pada setiap mutasi di aplikasi.
  • Bungkus setiap konsumer use() baru dengan error boundary, karena Promise yang ditolak sekarang akan muncul melalui boundary tersebut, bukan tertangkap diam-diam di dalam useEffect.

Tim yang lebih dulu memigrasikan halaman-halaman dengan banyak form melaporkan penurunan boilerplate paling besar, karena useActionState saja biasanya menghilangkan tiga panggilan useState terpisah per form.

Actions dan use juga berpadu baik dengan React Query atau SWR. Model ini tidak mengharuskan Anda meninggalkan data layer yang sudah ada; use dapat membongkar Promise yang sudah dihasilkan dan di-cache oleh library query tersebut.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa sebenarnya yang dilakukan hook use() di React 19?

Fungsi use membaca nilai resolve dari sebuah Promise atau nilai Context saat ini selama render. Jika diberikan Promise yang masih pending, komponen akan suspend sampai Promise itu resolve, dan Suspense boundary yang membungkusnya menampilkan fallback sementara menunggu. Berbeda dari hook lain, use bisa dipanggil secara kondisional atau di dalam loop, sehingga lebih fleksibel dibanding useContext untuk membaca Context.

Mengapa komponen saya suspend terus menerus saat memakai hook use()?

Ini hampir selalu terjadi karena Promise baru dibuat pada setiap render, bukan di-cache. React mengharapkan instance Promise yang sama di setiap render ulang agar bisa melacak apakah Promise itu sudah resolve. Perbaiki dengan membuat Promise sekali saja di luar jalur render, misalnya di komponen induk, route loader, atau Map berdasarkan parameter request, lalu turunkan instance yang sama sebagai prop.

Apa perbedaan useActionState dengan mengelola state secara manual?

useActionState membungkus fungsi Action async dan secara otomatis melacak hasil, error, serta flag pending-nya, menggantikan apa yang sebelumnya membutuhkan beberapa useState terpisah ditambah logika try catch manual. Hook ini juga terintegrasi langsung dengan prop action pada form, sehingga React mereset status pending setelah action selesai tanpa kode tambahan.

Bisakah useOptimistic dipakai di luar pengiriman form?

Bisa. useOptimistic bekerja dengan Action apa pun, tidak hanya pengiriman form, termasuk klik tombol yang dibungkus startTransition atau panggilan di dalam useActionState. Syaratnya adalah setter dipanggil di dalam sebuah Action, karena nilai optimistik hanya kembali otomatis setelah Action tersebut selesai atau melempar error.

Apakah saya perlu menulis ulang seluruh data fetching untuk mengadopsi Actions React 19?

Tidak, adopsi bisa dilakukan bertahap. Form dan tombol dengan boilerplate state pending dan error manual paling banyak adalah titik awal terbaik karena pengurangannya langsung terlihat dan perubahannya terisolasi. Actions dan use juga berpadu dengan library data yang sudah ada seperti React Query, karena use cukup membongkar Promise yang sudah dihasilkan library tersebut.