Hãy tưởng tượng, bạn đang xây dựng form trong React.
Gõ vào input thì hiển thị bình thường, submit thì lấy được data… Mọi thứ có vẻ ổn.
Nhưng rồi bạn thêm vài field, thêm vài state, mỗi cái lại một useState, và bỗng dưng component của bạn trông như thế này:
const [title, setTitle] = useState('');
const [category, setCategory] = useState('Work');
const [priority, setPriority] = useState('Medium');
const [description, setDescription] = useState('');
const [dueDate, setDueDate] = useState('');
const [assignee, setAssignee] = useState('');
// ...còn nữa
Có gì đó không ổn. Và đúng là không ổn thật. Trước khi đến cách làm tốt hơn, hãy hiểu rõ thứ nền tảng nhất: Controlled vs Uncontrolled Input.
Controlled Input là gì?
Controlled Input là input mà giá trị của nó được React kiểm soát hoàn toàn thông qua state.
Mỗi lần bạn gõ một ký tự:
React re-render, input hiển thị giá trị mới từ state React là nguồn sự thật duy nhất (single source of truth). DOM không tự quản lý gì cả — nó chỉ hiển thị những gì React bảo.
Nếu bạn thêm value={title} nhưng quên onChange, React sẽ cảnh báo ngay:
Lúc này input sẽ bị “đóng băng” — bạn gõ không có gì xảy ra cả.
Nếu thực sự muốn read-only → thêm readOnly prop:
Lúc này React không cảnh báo, và rõ ràng về ý định: "input này chỉ để hiển thị, không cho sửa."
Uncontrolled Input là gì?
Uncontrolled Input là input mà trình duyệt tự quản lý giá trị, React không can thiệp trực tiếp.
Không có value={...} → đây là uncontrolled input. Trình duyệt tự hiển thị những gì bạn gõ. React chỉ “biết” về giá trị khi bạn hỏi nó — qua e.target.value trong onChange, hoặc qua ref.
Nhưng tại sao vẫn submit được bình thường?
Vì khi bạn có onChange handler cập nhật state, thực chất bạn đang “nghe lén” DOM rồi lưu vào state thủ công. Khi submit, bạn đọc từ state đó — và mọi thứ vẫn hoạt động.
3 cách collect form data — từ ngây ngô đến pro
Cách 1: HTML form submit thuần (DOM)
Đây là cách “ngây ngô” nhất — không dùng state, để form HTML tự xử lý.
Bạn đọc giá trị trực tiếp từ DOM thông qua e.target.fieldName.value. Không cần state, không cần onChange.
Cách này cũng dùng được với FormData:
Hoặc dùng ref — cũng là uncontrolled, nhưng đọc giá trị qua useRef thay vì e.target:
useRef tạo ra object { current: null }, khi React mount input vào DOM thì tự gán DOM element vào titleRef.current. Từ đó bạn đọc .value bất cứ lúc nào mà không cần onChange, không cần state, không trigger re-render.
Khi nào dùng? Form cực kỳ đơn giản, không cần validate real-time, không cần disable/enable button theo state. Hiếm gặp trong React app thực tế.
Cách 2: Mỗi field một useState (pro thường)
Đây là cách nhiều người học React làm đầu tiên khi cần controlled inputs.
Mỗi input có state riêng, handler riêng. Rõ ràng, dễ đọc. Nhưng khi form có 6–8 field, bạn sẽ thấy component phình to nhanh chóng.
Cách 3: Dùng một formData object (pro hơn)
Thay vì nhiều state riêng lẻ, gom tất cả vào một state object duy nhất:
Điểm mấu chốt là handleChange dùng chung cho tất cả input:
[e.target.name] là computed property name — lấy giá trị của name attribute trên input làm key. Nhờ vậy một handler duy nhất xử lý được mọi field, miễn là name trên input khớp với key trong formData.
Thêm field mới? Chỉ cần thêm vào useState ban đầu và thêm input với đúng name — handleChange không cần chỉnh gì.
Cách 4 (bonus): action trong React Router
Nếu bạn dùng React Router v6.4+, có một cách hiện đại hơn: dùng action — một function chạy phía client khi form submit, nhận Request object giống như server.
Với cách này, component không cần state, không cần handler — toàn bộ logic submit được tách ra ngoài vào action. Form trở nên rất gọn.
Khi nào dùng? Khi bạn đã dùng React Router và muốn tổ chức code theo hướng data-fetching tập trung, hoặc khi chuẩn bị chuyển sang framework như Remix.
Tóm tắt
Trong thực tế, cách 3 (formData object) là lựa chọn phổ biến nhất cho form thông thường trong React — đủ linh hoạt, không quá phức tạp, và dễ mở rộng.
Bước tiếp theo
Sau khi nắm vững cách collect data, bạn có thể tìm hiểu thêm:
Validation: kiểm tra dữ liệu trước khi submit (custom hoặc dùng thư viện như zod) React Hook Form: thư viện quản lý form phổ biến, hiệu năng cao, tích hợp validation React Router actions & loaders: mô hình data flow hiện đại khi dùng React Router v6.4+