Hai hook bù trừ cho nhau khi làm form bằng Server Actions:
useActionState (React, dùng ở Client Component) — bọc một action, giữ state trả về của nó (lỗi validation, kết quả) qua các lần submit.
const [state, formAction] = useActionState(createUser, { error: null })
return <form action={formAction}>...{state.error && <p>{state.error}</p>}</form>useFormStatus (react-dom) — cho biết form cha đang pending hay không, để vô hiệu nút / hiện spinner.
function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? 'Đang gửi…' : 'Gửi'}</button>
}Tại sao useFormStatus phải ở component con: nó đọc trạng thái của <form> cha gần nhất qua context. Nếu gọi ngay trong cùng component chứa <form>, nó không nằm bên trong form đó nên luôn trả pending: false. Vì vậy phải tách nút submit thành component con đặt trong <form>.
Lưu ý: useFormState là tên cũ; React 19 đổi sang useActionState.
The two hooks complement each other when building forms with Server Actions:
useActionState (React, in a Client Component) — wraps an action and keeps its returned state (validation errors, results) across submits.
const [state, formAction] = useActionState(createUser, { error: null })
return <form action={formAction}>...{state.error && <p>{state.error}</p>}</form>useFormStatus (react-dom) — tells you whether the parent form is pending, to disable the button / show a spinner.
function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? 'Submitting…' : 'Submit'}</button>
}Why useFormStatus must be in a child: it reads the status of the nearest parent <form> via context. If called in the same component that renders <form>, it isn't inside that form, so it always returns pending: false. Hence you split the submit button into a child placed inside <form>.
Note: useFormState is the old name; React 19 renamed it to useActionState.