Menu

useActionState trong React: form, lỗi và trạng thái chờ

useActionState bọc một action bất đồng bộ, giữ giá trị mà nó trả về gần nhất, và cho bạn biết nó có đang chạy hay không. Tìm hiểu cú pháp, form action, cách trả về lỗi kiểm tra dữ liệu, và cách gọi nó bên ngoài form.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

useActionState là một hook của React 19, chạy một hàm bất đồng bộ (một action), lưu bất cứ thứ gì nó trả về làm state, và cho bạn biết nó có còn đang chạy hay không. Bạn gọi nó dưới dạng const [state, formAction, isPending] = useActionState(action, initialState) rồi truyền formAction cho một <form>.

Gửi form trống trước: action trả về một object lỗi và thông báo hiện ra. Sau đó gõ một địa chỉ có @ rồi gửi lại. Trong một giây, nút hiện "Subscribing..." và bị vô hiệu hóa.

Cú pháp

const [state, formAction, isPending] = useActionState(action, initialState, permalink?);
  • action là hàm cần chạy. React gọi nó dưới dạng action(previousState, ...args). Bất cứ thứ gì nó trả về (hoặc resolve thành, nếu là hàm async) sẽ trở thành state mới.
  • initialState là state trước khi action chạy lần nào. Hãy dùng cùng cấu trúc mà action trả về, để component đọc được state.message ngay cả ở lần render đầu.
  • state là kết quả mới nhất.
  • formAction là phiên bản đã được bọc của action. Truyền nó cho <form action> hoặc <button formAction>, hoặc tự gọi nó.
  • isPending là true từ lúc action bắt đầu cho đến khi nó kết thúc.
  • permalink là tùy chọn và chỉ có ý nghĩa với Server Function trong một framework: URL mà form sẽ chuyển tới nếu được gửi trước khi JavaScript tải xong.

Action nhận state trước đó làm đối số đầu tiên. Đây là lỗi phổ biến nhất khi chuyển một form sang cách này: viết async function save(formData) khiến formData chứa state cũ, và formData.get sẽ ném lỗi.

Đọc form bằng FormData

Khi formAction được dùng làm action của form, React gọi action của bạn với FormData của form. Mọi trường có name đều nằm trong đó. Bạn không cần useState cho các ô nhập hay trình xử lý onChange, và không cần gọi e.preventDefault(): React tự xử lý sự kiện submit.

Bỏ chọn Spicy rồi gửi lại: khóa spicy biến mất khỏi dữ liệu được ghi log, vì một checkbox không được chọn sẽ không gửi gì cả. Action cũng đọc previousState.count để cộng thêm một, đó là lý do state trước đó đứng đầu.

Trả về lỗi kiểm tra dữ liệu

Việc kiểm tra dữ liệu nên nằm trong action. Trả về các lỗi như một phần của state, và trả về cả những giá trị người dùng đã gõ để form điền lại chúng. Sau khi một form action chạy xong, React đặt lại các trường uncontrolled của form, nên một trường không có defaultValue={state.values...} sẽ trống trơn sau một lần gửi thất bại.

Gõ một cái tên chỉ có một chữ cái và một mật khẩu ngắn, rồi gửi. Cả hai lỗi hiện ra và cả hai trường vẫn giữ những gì bạn đã gõ. Bỏ defaultValue={state.values.name} rồi gửi lại: trường tên tự xóa trắng sau khi báo lỗi.

Trong một ứng dụng thật, hãy để máy chủ là bên quyết định cuối cùng. Kiểm tra phía client cho phản hồi nhanh, nhưng ai cũng có thể gửi request mà không cần qua form của bạn.

Gọi action bên ngoài form

formAction là một hàm, nên một nút hay một effect cũng có thể gọi nó. Khi tự gọi, hãy bọc lời gọi trong startTransition. Form làm việc đó giúp bạn; một onClick thông thường thì không, và thiếu transition thì isPending không cập nhật đúng (React ghi một lỗi về chuyện này ở development).

Click Load more vài lần. Mỗi lần gọi bắt đầu từ state mà lần trước trả về, nên các trang được nối thêm theo đúng thứ tự. Nếu bạn gọi action lần nữa khi nó vẫn đang chạy, React đưa lời gọi vào hàng đợi và chạy nó sau lời gọi hiện tại, mỗi lần với state mới nhất.

useActionState đến từ đâu

Trước khi React 19 ra mắt, hook này có trong các bản build canary với tên useFormState, import từ react-dom. Nó được đổi tên thành useActionState, chuyển sang react và có thêm giá trị isPending. Nếu bạn gặp code cũ như dưới đây, chỉ cần sửa câu lệnh import và tên hook:

// React canary, before 19
import { useFormState } from 'react-dom';
const [state, formAction] = useFormState(action, initialState);

// React 19
import { useActionState } from 'react';
const [state, formAction, isPending] = useActionState(action, initialState);

Để hiển thị trạng thái chờ trong một nút submit lồng sâu, useFormStatus từ react-dom đọc trạng thái của form cha mà không cần props. Trang tổng quan React 19 liệt kê nó cùng các tính năng form khác.

Lỗi và khi nào nên dùng thứ khác

Nếu action ném lỗi, React hiển thị error boundary gần nhất thay vì cập nhật state. Với những lỗi người dùng có thể tự sửa, như dữ liệu không hợp lệ hay tên người dùng đã có người khác đăng ký, hãy trả chúng về dưới dạng state, như trong ví dụ đăng ký. Còn những sự cố thật thì cứ để chúng ném lỗi.

useActionState dành cho kết quả của một action. Với giá trị cần cập nhật ngay lập tức trong lúc action chạy, như số lượt thích phải tăng ngay khi click, hãy kết hợp nó với useOptimistic. Với ô nhập thay đổi theo từng phím gõ và được đọc trong lúc render, useState thông thường và một controlled input vẫn là công cụ phù hợp.

Lỗi thường gặp

Quên tham số state trước đó. async function save(formData) nhận state trước đó trong formData. Với form, chữ ký hàm luôn là (previousState, formData).

Ô nhập không có name. FormData chỉ chứa các trường có thuộc tính name. Một trường chỉ có id sẽ không có trong formData.get(...), hàm này trả về null.

Dùng controlled input ở mọi nơi. Bạn vẫn có thể dùng value và onChange bên trong form action, nhưng không bắt buộc. Các trường uncontrolled với name và defaultValue hợp với action hơn, và việc React đặt lại form sau khi submit chỉ ảnh hưởng tới các trường uncontrolled.

Đọc state bên trong action. state của component là giá trị từ lần render đã tạo ra handler. Bên trong action, hãy dùng previousState, luôn là kết quả của lời gọi ngay trước đó, kể cả khi nhiều lời gọi đang xếp hàng.

Ném lỗi cho những lỗi có thể đoán trước. Một lỗi bị ném sẽ thay form bằng error boundary gần nhất. Sai mật khẩu không phải là sự cố sập ứng dụng; hãy trả nó về dưới dạng state.

Câu hỏi thường gặp

useActionState trả về những gì?

Một mảng gồm ba phần tử: state hiện tại (giá trị mà action trả về gần nhất, hoặc state ban đầu khi action chưa chạy), một action mới để truyền cho form hoặc tự gọi, và isPending, có giá trị true trong lúc action đang chạy.

Action nhận những đối số nào?

Đầu tiên là state trước đó, sau đó là bất cứ thứ gì action được gọi kèm. Trong một <form action={formAction}>, đối số thứ hai đó là FormData của form.

useActionState và useFormState khác nhau thế nào?

Chúng là cùng một hook. Hook này xuất hiện trong các bản canary của React với tên useFormState thuộc react-dom, rồi được đổi tên thành useActionState và chuyển sang react ở React 19, đồng thời có thêm giá trị isPending.

Có thể dùng useActionState mà không cần form không?

Có. Hãy tự gọi action được trả về, bên trong startTransition, chẳng hạn từ onClick của một nút. Nếu không có transition, isPending không cập nhật đúng và React ghi một lỗi ở development.

Vì sao form bị xóa trắng sau khi gửi?

Sau khi một form action chạy xong, React đặt lại các trường uncontrolled của form, giống như trình duyệt làm sau một lần submit bình thường. Để giữ giá trị sau khi có lỗi, hãy trả nó về trong state và truyền vào defaultValue của trường.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu