Menu

Form trong React: input, checkbox, select, kiểm tra dữ liệu

Xây dựng form trong React với input được kiểm soát: ô văn bản, textarea, select, checkbox và radio, một đối tượng state cho nhiều ô nhập, gửi form bằng onSubmit, hiển thị lỗi kiểm tra dữ liệu và form action của React 19.

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.

Một form (biểu mẫu) trong React giữ giá trị của từng ô nhập trong state: ô nhập hiển thị value={state} và cập nhật nó bằng onChange. Khi người dùng gửi form, một handler onSubmit gọi e.preventDefault() để chặn việc tải lại trang rồi dùng các giá trị đã có sẵn trong state.

Gõ một cái tên rồi nhấn Enter hoặc click nút. Sau đó xóa dòng e.preventDefault();: phần xem trước sẽ hiện các trường mà trình duyệt lẽ ra đã gửi đi khi gửi form theo cách gốc, và trên một trang thật thì trang cũng sẽ bị tải lại.

Một ô nhập lấy giá trị từ state được gọi là input được kiểm soát (controlled input). Cách còn lại, để DOM tự giữ giá trị và đọc nó sau, được trình bày trong component controlled và uncontrolled.

Ô văn bản và textarea

Mọi ô văn bản đều hoạt động theo cùng một cách: value lấy từ state, onChange đọc e.target.value. <textarea> cũng theo đúng mẫu này. Trong HTML, văn bản của nó nằm giữa hai thẻ, nhưng trong React bạn truyền nó qua value.

Vì bạn tự đặt giá trị, bạn có thể thay đổi những gì người dùng gõ trước khi nó hiện ra: ô tiêu đề biến mọi chữ cái thành chữ hoa. Bộ đếm ký tự cập nhật sau mỗi lần gõ phím vì body là state và mỗi thay đổi đều gây render lại.

Select

<select> nhận value ngay trên phần tử select, không phải selected trên một option. e.target.value là value của option được chọn.

Với select chọn nhiều, hãy thêm multiple và truyền một mảng vào value. Đọc các lựa chọn bằng Array.from(e.target.selectedOptions, (o) => o.value).

Checkbox và radio

Checkbox được kiểm soát bằng checked, không phải value, và handler của nó đọc e.target.checked, có giá trị true hoặc false. Các nút radio trong cùng một nhóm dùng chung một name; mỗi nút được chọn khi state bằng giá trị của nó.

Đọc e.target.value trên checkbox là một lỗi thường gặp: với checkbox không có thuộc tính value, nó trả về chuỗi "on" dù ô có được đánh dấu hay không.

Một đối tượng state cho nhiều ô nhập

Một form có mười ô nhập không cần mười lần gọi useState và mười handler. Hãy giữ một đối tượng, đặt cho mỗi ô nhập một name khớp với một khóa, và viết một handler dùng tên đó làm khóa được tính toán: [e.target.name].

Handler kiểm tra loại ô nhập để checkbox lưu giá trị boolean còn các ô khác lưu văn bản. Muốn thêm một ô, bạn chỉ cần thêm một khóa vào đối tượng ban đầu và một ô nhập có cùng name; handler không cần sửa gì. Toán tử spread giữ nguyên mọi trường khác (xem cập nhật mảng và đối tượng).

Gửi form

Đặt logic gửi form trong onSubmit của form, không phải onClick của nút. onSubmit cũng kích hoạt khi người dùng nhấn Enter trong một ô văn bản, điều mà handler click sẽ bỏ lỡ. Một <button> bên trong form mặc định là nút gửi; hãy đặt type="button" cho các nút khác để chúng không gửi form.

Mở console, gõ mật khẩu rồi nhấn Enter: form ghi ra toàn bộ đối tượng. Click "Clear" thì không ghi gì, vì nút đó là type="button". Bỏ thuộc tính này đi thì Clear cũng bắt đầu gửi form.

Kiểm tra dữ liệu và hiển thị lỗi

Kiểm tra dữ liệu (validation) đơn giản là một hàm nhận các giá trị và trả về một đối tượng chứa thông báo lỗi. Chạy nó khi gửi form, lưu kết quả vào state, và hiển thị từng thông báo bên dưới ô nhập tương ứng. Vô hiệu hóa nút gửi trong lúc request đang chạy để double click không gửi form hai lần.

Click Save khi các ô còn trống để thấy cả hai thông báo, rồi điền thông tin vào. saveProfile đóng vai một request thật: nó chờ 800 mili giây rồi resolve. Thuộc tính noValidate tắt các bong bóng kiểm tra của chính trình duyệt để chỉ thông báo của bạn hiện ra; bỏ nó đi và thêm required vào một ô nhập để so sánh.

Lỗi cũng có thể được tính ngay trong lúc render thay vì lưu lại, ví dụ const emailError = form.email && !form.email.includes('@'), cách này hiện thông báo ngay khi người dùng gõ. Hãy chọn thời điểm phù hợp với form: kiểm tra khi gửi thì nhẹ nhàng hơn, còn kiểm tra khi gõ cho phản hồi nhanh hơn.

Form action trong React 19

React 19 bổ sung một cách khác để xử lý form. Truyền một hàm vào prop action của form. React tự chặn việc tải lại trang và gọi hàm đó với một đối tượng FormData chứa mọi ô nhập có name. Các ô nhập hoàn toàn không cần state: chúng là uncontrolled, và React xóa chúng sau khi action chạy xong.

Gõ bình luận của bạn rồi đăng. Danh sách hiện văn bản bạn vừa gõ, còn ô nhập quay về Nice docs: sau khi action resolve, React đặt lại các ô nhập uncontrolled về defaultValue của chúng. Action có thể là hàm async, và trong lúc nó chạy React coi bản cập nhật là một transition.

Để hiện trạng thái đang chờ hoặc lỗi từ action, hãy bọc nó bằng useActionState, hook này trả về kết quả mới nhất, một action đã được bọc để dùng cho form, và cờ isPending. Một nút nằm sâu bên trong form cũng có thể đọc trạng thái đang chờ bằng useFormStatus từ react-dom:

import { useFormStatus } from 'react-dom';

function SubmitButton() {
    const { pending } = useFormStatus();
    return <button disabled={pending}>{pending ? 'Posting...' : 'Post'}</button>;
}

useFormStatus không được tải trong các trình soạn thảo ở đây, nên nó chỉ xuất hiện trong khối code tĩnh này. Nó phải được gọi từ một component render bên trong <form>, không phải trong component render ra form.

State được kiểm soát với onSubmit và form action với FormData đều dùng được trong React 19. Ô nhập được kiểm soát hợp với những form phản hồi ngay khi bạn gõ (kiểm tra trực tiếp, bộ đếm ký tự, các ô phụ thuộc lẫn nhau). Action hợp với những form mà bạn chỉ cần giá trị khi người dùng gửi.

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

Làm sao lấy giá trị của một input trong React?

Lưu nó trong state và cập nhật từ onChange: <input value={name} onChange={e => setName(e.target.value)} />. Khi đó văn bản hiện tại luôn nằm trong name. Với checkbox, hãy đọc e.target.checked thay vì e.target.value.

Làm sao ngăn form tải lại trang trong React?

Gọi e.preventDefault() ở đầu handler onSubmit. Nếu không, trình duyệt sẽ gửi form theo cách thông thường, tức là chuyển trang và tải lại trang.

Làm sao xử lý nhiều ô nhập bằng một state trong React?

Giữ một đối tượng trong state, đặt cho mỗi ô nhập một name khớp với một khóa, và dùng một handler với khóa được tính toán: setForm({ ...form, [e.target.name]: e.target.value }).

Vì sao textarea trong React dùng value thay vì children?

Trong HTML, văn bản của textarea nằm giữa hai thẻ, nhưng trong React bạn truyền nó qua value (hoặc defaultValue), giống như với input. Nhờ vậy mọi ô văn bản trong form React đều hoạt động giống nhau.

Form action trong React 19 là gì?

Trong React 19, bạn có thể truyền một hàm vào <form action={fn}>. React chặn việc tải lại trang, gọi hàm đó với FormData của form, và đặt lại các ô nhập uncontrolled khi hàm chạy xong. useActionState bổ sung thêm cờ đang chờ và kết quả trả về.

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