Skip to content

Controlled vs Uncontrolled Input trong React

Và 3 cách collect form data từ "ngây ngô" đến "pro max".
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ự:
onChange được gọi
setTitle cập nhật state
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.
Controlled
Uncontrolled
Ai quản lý giá trị?
React state
DOM (trình duyệt)
Cách đọc giá trị
Từ state
e.target.value hoặc ref
Phù hợp khi
Cần validate real-time, disable button…
Form đơn giản, ít tương tác
There are no rows in this table

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ốthandleChange dùng chung cho tất cả input:
[e.target.name]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 namehandleChange 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

Cách
State?
Phù hợp khi
HTML form + DOM
Không
Form cực đơn giản, không cần React quản lý
Nhiều useState
Form nhỏ, ít field, cần rõ ràng từng field
Một formData object
Form nhiều field, muốn code gọn
React Router action
Không
Đã dùng React Router, muốn tách logic
There are no rows in this table
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+
Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.