Nextjs Forms Server Actions Optimistic UI Pending States
Definisi
Di Next.js App Router, form tidak selalu harus dikelola penuh di client. Ada beberapa lapisan yang sering dipakai bersama:
Client-managed form: state input, validasi ringan, dan interaksi UI dikelola di browser
Server Action: fungsi server yang menerima submit form dan menjalankan perubahan data di server
Optimistic UI: tampilan sementara yang langsung berubah sebelum response server selesai
Pending state: indikator bahwa submit sedang diproses, supaya user tidak menebak-nebak status request
Topik ini penting karena form yang sehat bukan cuma soal “bisa submit”. Yang perlu dijaga juga adalah:
kapan input memang butuh state di client
kapan logic sebaiknya dipindah ke server
bagaimana UI tetap terasa cepat saat server masih bekerja
bagaimana error, rollback, dan revalidation disusun tanpa bikin state liar
Kapan form dikelola di client
Form dikelola di client kalau interaksinya bergantung pada state browser atau perlu respons instan sebelum submit.
Contoh situasi yang cocok:
validasi per karakter atau per field
show/hide password
input yang saling memengaruhi, misalnya country → city
draft lokal yang disimpan sementara
form multi-step dengan navigasi lokal
input yang perlu preview langsung, misalnya file upload preview atau markdown preview
Pola client-managed biasanya memakai useState, useReducer, atau library form seperti React Hook Form. Tujuannya bukan sekadar “lebih modern”, tapi karena browser memang tempat yang paling tepat untuk state interaktif yang bersifat lokal.
Tanda form sebaiknya tetap di client
aturan validasi terlalu bergantung pada interaksi langsung user
UI harus merespons tanpa round-trip server
nilai form dipakai untuk kalkulasi visual di halaman yang sama
error helper harus muncul saat user masih mengetik
Kalau semua hal dipaksa lewat server, form akan terasa berat dan patah-patah.
Kapan Server Action dipakai
Server Action dipakai saat submit form adalah operasi yang mengubah data di server.
Contoh yang cocok:
create post
update profil
ubah status order
simpan komentar
menambah item ke cart yang tersimpan di backend
Server Action paling pas kalau form punya karakter berikut:
data harus divalidasi dan disimpan di server
hasil perubahan harus memengaruhi cache atau halaman lain
operasi butuh akses ke database, secret, session, atau service internal
logika submit ingin dekat dengan komponen yang memakainya tanpa bikin route handler terpisah
tsx
'use server'import { revalidatePath } from 'next/cache'export async function updateProfile(formData: FormData) { const name = String(formData.get('name') ?? '') if
Catatan penting: contoh di atas memperlihatkan ide, bukan satu-satunya bentuk implementasi. Di proyek nyata, Anda sering perlu menata ulang bagaimana data final masuk lagi setelah action selesai.
Pending states
Pending state adalah sinyal UI bahwa proses submit sedang berjalan.
Bentuk yang umum:
tombol submit menjadi disabled
label tombol berubah menjadi “Menyimpan…”
spinner atau progress ring muncul
input tertentu dikunci sementara
banner kecil menjelaskan bahwa request belum selesai
Pending state berbeda dari optimistic UI:
pending state memberi tahu bahwa request masih berjalan
optimistic UI membuat layar terasa sudah berubah lebih dulu
Keduanya sering dipakai bersama.
Contoh penggunaan useTransition
tsx
'use client'import { useTransition } from 'react'export function SaveButton({ action }: { action: (formData: FormData) => Promise<void> }) { const
Kalau form submit mengubah data yang dirender ulang dari server, pending state biasanya cukup untuk memberi feedback tanpa harus menunggu full page reload mental di kepala user.
Jebakan umum saat menunggu response server
1. UI terlihat cepat, tapi data final belum sinkron
Optimistic update bisa membuat UI tampak benar padahal data server belum selesai. Kalau revalidation tidak dipanggil atau scope-nya salah, data final bisa tertinggal.
2. Optimistic state tidak punya rollback
Kalau request gagal, state sementara harus dibatalkan. Kalau tidak, user melihat data palsu yang seolah-olah sudah tersimpan.
3. Pending state terlalu luas
Kadang satu pending flag dipakai untuk semua area UI. Akibatnya, satu submit kecil malah mematikan seluruh form atau halaman.
4. Double submit
Tanpa disable button atau kontrol in-flight request, user bisa mengirim request dua kali. Ini sering berujung duplikasi data.
5. Error server tidak dipetakan ke form
Error dari server yang hanya dilempar sebagai exception sering hilang dari UX. Lebih baik error dikembalikan dalam bentuk yang bisa dibaca komponen form.
6. Form client terlalu gemuk
Semua logic ditaruh di client, padahal sebagian besar harusnya server-side. Hasilnya bundle membengkak dan validation logic jadi berulang.
7. Terlalu cepat menganggap response server sebagai sumber kebenaran satu-satunya
Dalam alur optimistic, UI lokal memang boleh mendahului server. Yang penting adalah final reconciliation: data server tetap harus jadi referensi akhir.
Pola keputusan praktis
Pakai aturan singkat ini saat merancang form:
butuh interaksi lokal cepat → kelola di client
butuh mutation data → pakai Server Action
ingin UI terasa instan → tambahkan optimistic UI
ingin user paham request masih berjalan → tambahkan pending state
ingin data konsisten setelah mutation → revalidate path atau tag yang relevan
Kalau form Anda hanya memvalidasi lalu mengirim data, Server Action sering cukup. Kalau ada interaksi kompleks sebelum submit, gabungkan client state dengan Server Action.
Alur produksi yang sehat
Alur yang umum dipakai di proyek nyata biasanya begini:
user mengisi form di client
client menjalankan validasi ringan dan menyiapkan pending state
form submit ke Server Action
UI menampilkan optimistic update bila cocok
server memvalidasi ulang dan menyimpan data
server mengembalikan hasil atau error yang jelas
cache atau segment yang terdampak direvalidate
UI final disinkronkan dengan data server
Kalau urutan ini dijaga, form terasa cepat tanpa mengorbankan konsistensi.
Catatan implementasi
Jangan menyimpan secret atau logic sensitif di client hanya demi kenyamanan submit.
Jangan pakai optimistic UI untuk operasi yang sulit dibatalkan kalau gagal.
Jangan lupa bedakan error validasi dengan error server/transport.
Jangan matikan semua kontrol form hanya karena satu field sedang pending.
Jangan lupa revalidate kalau mutation memengaruhi data yang dibaca ulang di route lain.
Referensi terkait
Next.js Routing, Server/Client Components, dan Data Fetching
Next.js Caching, Revalidation, Server Actions, dan Mutations Flow
React.js Forms: Controlled vs Uncontrolled, Lifting State, Validation, dan Submit Flow
Fetch API, Async Data Flow, JSON Parsing, dan Error Handling Dasar
(
!
name.
trim
()) {
return { ok: false, message: 'Nama wajib diisi' }
}
// simpan ke database
revalidatePath('/profile')
return { ok: true }
}
?:
boolean
}
export function CommentForm({ comments, addComment }: {