Nguyễn Đình Cường
0 comment
09 Nov, 2026
Form là nơi bạn dùng state nhiều nhất trong React: đăng ký, đăng nhập, tìm kiếm, thanh toán. Cách làm chuẩn là controlled component, trong đó React giữ giá trị của các ô nhập. Bài này đi từ một ô nhập đến form nhiều trường có kiểm tra hợp lệ.
Trong HTML thuần, ô nhập tự giữ giá trị. Với controlled component, giá trị nằm trong state, ô nhập chỉ hiển thị nó và báo mỗi thay đổi lên cho React:
const [email, setEmail] = useState('');
<input type='email' value={email} onChange={e => setEmail(e.target.value)} />Vì giá trị luôn đến từ state, bạn dễ dàng kiểm tra, định dạng, xóa trắng hay đặt giá trị từ bên ngoài.
Khai báo từng state cho mỗi ô sẽ dài dòng. Gom thành một object và dùng thuộc tính name để cập nhật chung:
function DangKy() {
const [form, setForm] = useState({ ten: '', email: '', matKhau: '', dongY: false });
function doi(e) {
const { name, value, type, checked } = e.target;
setForm(truoc => ({
...truoc,
[name]: type === 'checkbox' ? checked : value,
}));
}
return (
<form>
<input name='ten' value={form.ten} onChange={doi} />
<input name='email' type='email' value={form.email} onChange={doi} />
<input name='matKhau' type='password' value={form.matKhau} onChange={doi} />
<label>
<input name='dongY' type='checkbox' checked={form.dongY} onChange={doi} />
Tôi đồng ý điều khoản
</label>
</form>
);
}Cú pháp [name]: value (computed property name) dùng giá trị của biến name làm khóa. Một hàm xử lý duy nhất phục vụ mọi ô. Lưu ý checkbox dùng checked thay vì value.
Tách logic kiểm tra thành một hàm thuần trả về object lỗi:
function kiemTra(form) {
const loi = {};
if (!form.ten.trim()) loi.ten = 'Vui lòng nhập họ tên';
if (!/^\S+@\S+\.\S+$/.test(form.email)) loi.email = 'Email chưa đúng định dạng';
if (form.matKhau.length < 8) loi.matKhau = 'Mật khẩu tối thiểu 8 ký tự';
if (!form.dongY) loi.dongY = 'Bạn cần đồng ý điều khoản';
return loi;
}Hàm này dễ kiểm thử vì không phụ thuộc React. Biểu thức chính quy cho email ở trên chỉ kiểm tra hình dạng cơ bản, việc xác nhận email thật vẫn là việc của máy chủ.
const [loi, setLoi] = useState({});
const [daGui, setDaGui] = useState(false);
async function guiForm(e) {
e.preventDefault();
const ketQua = kiemTra(form);
setLoi(ketQua);
if (Object.keys(ketQua).length > 0) return;
setDaGui(true);
try {
const res = await fetch('/api/dang-ky', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form),
});
if (!res.ok) throw new Error('Đăng ký thất bại');
} catch (err) {
setLoi({ chung: err.message });
} finally {
setDaGui(false);
}
}Và trong JSX, hiển thị lỗi ngay dưới ô tương ứng, kèm thuộc tính cho khả năng tiếp cận:
<input name='email' value={form.email} onChange={doi}
aria-invalid={Boolean(loi.email)} aria-describedby='loi-email' />
{loi.email && <p id='loi-email' role='alert'>{loi.email}</p>}
<button type='submit' disabled={daGui}>
{daGui ? 'Đang gửi...' : 'Đăng ký'}
</button>Vô hiệu hóa nút khi đang gửi để tránh người dùng bấm nhiều lần và tạo yêu cầu trùng lặp.
Trải nghiệm tốt thường là kiểm tra khi người dùng rời khỏi ô (onBlur) và khi gửi, thay vì báo đỏ ngay từ ký tự đầu tiên. Bạn có thể lưu thêm một object daCham ghi nhận ô nào đã được chạm vào, và chỉ hiện lỗi của những ô đó.
Form nhỏ làm tay hoàn toàn ổn và giúp bạn hiểu rõ cơ chế. Khi form phức tạp (nhiều bước, mảng trường động, hàng chục ô), các thư viện như React Hook Form kết hợp bộ kiểm tra schema như Zod giảm đáng kể lượng mã và số lần render. Hãy học cách làm tay trước để biết thư viện đang giải quyết vấn đề gì.
Với form đơn giản, bạn có thể để trình duyệt giữ giá trị và đọc khi gửi bằng FormData:
function guiNhanh(e) {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));
console.log(data);
}Cách này ít mã hơn, phù hợp khi bạn không cần phản hồi từng lần gõ phím.
type và autoComplete để trình duyệt gợi ý và hiện bàn phím phù hợp trên điện thoại.Hãy bắt đầu với controlled component, gom state thành một object, tách hàm kiểm tra thuần, hiển thị lỗi gần ô nhập và nhớ vô hiệu hóa nút khi đang gửi. Mọi form phức tạp hơn đều xây trên các nguyên tắc đó. Đừng quên máy chủ vẫn phải kiểm tra lại dữ liệu.
Nếu muốn ráp form vào một ứng dụng đầy đủ có đăng nhập và API, bạn có thể tìm hiểu thêm tại các khóa học trên codebem.com.
Nguyễn Đình Cường
0 comment