Một component controlled là phần tử form có giá trị nằm trong state của React: bạn truyền value và cập nhật state trong onChange, nên React quyết định input hiển thị gì. Một component uncontrolled giữ giá trị trong DOM, giống HTML thuần: bạn truyền giá trị ban đầu defaultValue và đọc giá trị hiện tại sau đó qua một ref hoặc FormData.
Hãy gõ vào cả hai input. Đoạn văn bám theo input đầu tiên sau mỗi lần gõ phím, vì mỗi thay đổi đặt state và render lại. Input thứ hai chỉ thay đổi trong DOM; React không render, và bạn chỉ thấy giá trị của nó khi "Read both" yêu cầu đọc.
Input controlled
Với một input controlled, state của React là nguồn sự thật duy nhất. Luồng xử lý ở mỗi lần gõ phím là:
- Người dùng gõ, và trình duyệt phát ra một sự kiện input.
- Trình xử lý
onChangecủa bạn đọce.target.valuevà gọi hàm setter. - React render lại và ghi state mới trở lại vào
valuecủa input.
Vì code của bạn nằm giữa vòng lặp đó, bạn làm được những việc mà input uncontrolled không làm được: từ chối hoặc định dạng lại ký tự, hiển thị giá trị ở chỗ khác, vô hiệu hóa một nút cho tới khi giá trị hợp lệ, hoặc đặt giá trị từ code (một nút "Xóa", một gợi ý người dùng bấm vào).
Thử gõ chữ cái: chúng không bao giờ xuất hiện, vì trình xử lý loại bỏ mọi ký tự không phải chữ số trước khi đặt state. Nút Verify vẫn bị vô hiệu hóa cho tới khi có đúng sáu chữ số.
Controlled là cách các ví dụ form trên trang form sử dụng, và là lựa chọn mặc định trong hầu hết code React.
Input uncontrolled
Một input uncontrolled hoạt động như HTML thuần. React render nó một lần với defaultValue (hoặc defaultChecked cho checkbox và radio) rồi để yên. Để đọc giá trị, bạn giữ một ref tới phần tử, hoặc đọc mọi trường có tên từ form lúc submit bằng FormData.
Không có state cho các trường và không có onChange. Giá trị chỉ được thu thập khi form được submit. Bỏ chọn checkbox rồi gửi lại: khóa subscribe biến mất, vì một checkbox không được chọn hoàn toàn không có mặt trong FormData.
Thay đổi defaultValue sau lần render đầu tiên không ảnh hưởng gì tới input, vì React chỉ áp dụng nó khi phần tử được tạo. Để đặt lại một form uncontrolled, hãy gọi e.target.reset(), hoặc cho form một key mới để React tạo các input mới.
Input không gõ được
Nếu bạn truyền value mà không có onChange, input là controlled, nhưng không có gì cập nhật state cả. React ghi lại cùng một giá trị sau mỗi lần gõ phím, nên input trông như bị đóng băng.
Thử gõ vào từng trường. Trường đầu tiên không thay đổi. Trường thứ hai là cách sửa cho input controlled, và khi bạn gõ ở đó, trường đầu tiên và trường cuối cùng bám theo nó, vì cả ba đều đọc cùng một state. Trường thứ ba cho gõ vì nó là uncontrolled. Trường thứ tư cố ý báo rằng nó không thể chỉnh sửa.
Ở chế độ development, React ghi một cảnh báo cho input đầu tiên: "You provided a value prop to a form field without an onChange handler". Phần xem trước chạy như bản build production, nên bạn sẽ không thấy cảnh báo đó ở đây, nhưng sẽ thấy trên dev server của chính bạn.
Chuyển từ uncontrolled sang controlled
React quyết định một input có phải controlled hay không bằng cách nhìn vào value. Nếu value là undefined hoặc null, input là uncontrolled. Vì vậy một input có value ban đầu là undefined rồi sau đó thành một chuỗi sẽ chuyển từ uncontrolled sang controlled trong vòng đời của nó, và React cảnh báo ở chế độ development: "A component is changing an uncontrolled input to be controlled".
Cảnh báo này chỉ ra một bug thật. Đi theo chiều ngược lại, từ một chuỗi về undefined, sẽ khiến input trở lại uncontrolled, và React thôi ghi vào nó.
Gõ gì đó vào cả hai input rồi bấm từng nút Clear. Input thứ hai được làm trống. Input đầu tiên vẫn giữ chữ: đặt state thành undefined đã biến nó thành uncontrolled, nên React không còn kiểm soát những gì nó hiển thị.
Nguồn gốc thường gặp của undefined là dữ liệu chưa tải xong (value={user.name} trước khi có user) hoặc một trường của object chưa tồn tại. Hai cách sửa:
const [name, setName] = useState(''); // start with a string
<input value={user?.name ?? ''} /> // or fall back to one
Với checkbox, quy tắc tương tự áp dụng cho checked: khởi tạo nó bằng false, không phải undefined.
Input file luôn là uncontrolled
Một <input type="file"> không thể là controlled, vì trình duyệt không cho trang web đặt giá trị của nó (một trang web có thể tự chọn tệp trên ổ đĩa của bạn sẽ là một lỗ hổng bảo mật). Hãy đọc các tệp được chọn từ e.target.files trong onChange, hoặc qua một ref khi form được submit.
Chọn một hoặc hai tệp từ máy tính của bạn; không có gì được tải lên, ví dụ chỉ liệt kê tên và kích thước. Giá trị duy nhất bạn được phép đặt cho input file là chuỗi rỗng, và đó là cách nút Clear đặt lại nó.
Khi nào chọn loại nào
| Controlled | Uncontrolled | |
|---|---|---|
| Giá trị nằm ở | State của React | DOM |
| Đặt giá trị ban đầu | value / checked | defaultValue / defaultChecked |
| Đọc giá trị | Bất cứ lúc nào, từ state | Bằng ref, hoặc FormData lúc submit |
| Render sau mỗi lần gõ phím | Có | Không |
| Thay đổi giá trị từ code | Đặt state | Ghi vào DOM qua ref |
| Phù hợp cho | Kiểm tra hợp lệ trực tiếp, định dạng, các trường phụ thuộc | Form đơn giản đọc lúc submit, input file, form action |
Chọn controlled khi màn hình phải phản ứng với giá trị trong lúc người dùng gõ. Chọn uncontrolled khi bạn chỉ cần giá trị ở cuối, và đó cũng là cách form action của React 19 hoạt động: <form action={fn}> đưa cho hàm của bạn một FormData và đặt lại các trường uncontrolled sau đó.
Render sau mỗi lần gõ phím nghe có vẻ tốn kém, nhưng với một form bình thường thì không thành vấn đề. Nó chỉ thành vấn đề khi một cây component lớn và chậm render lại cùng input; khi đó hãy giữ state gần input, hoặc chuyển phần chậm vào một component riêng.
Hai từ này cũng dùng để mô tả chính các component của bạn. Một component nhận giá trị và onChange từ component cha qua props là được cha điều khiển (controlled); một component tự giữ state riêng là uncontrolled. Chuyển state lên trên để một component trở thành controlled chính là nội dung của lifting state up.
Câu hỏi thường gặp
Controlled component trong React là gì?
Là một phần tử form có giá trị hiện tại lấy từ state của React, được đặt bằng value (hoặc checked) và cập nhật qua onChange. State của React là nguồn sự thật duy nhất, nên bạn luôn biết giá trị và có thể thay đổi nó từ code.
Uncontrolled component trong React là gì?
Là một phần tử form tự giữ giá trị trong DOM, giống HTML thuần. Bạn cho nó giá trị ban đầu bằng defaultValue (hoặc defaultChecked) và đọc giá trị hiện tại khi cần, qua một ref hoặc FormData lúc submit.
Vì sao tôi không gõ được vào input React?
Bạn đã truyền value mà không có onChange. Input này là controlled, nên React liên tục đặt lại giá trị state sau mỗi lần gõ phím. Hãy thêm một onChange cập nhật state, chuyển sang defaultValue, hoặc thêm readOnly nếu nó thật sự không nên thay đổi.
Sửa lỗi "A component is changing an uncontrolled input to be controlled" thế nào?
value của input ban đầu là undefined (hoặc null) và sau đó trở thành một chuỗi. Hãy đảm bảo nó luôn là chuỗi: khởi tạo state bằng '', hoặc viết value={name ?? ''}.
Nên dùng input controlled hay uncontrolled?
Dùng input controlled khi giao diện phản ứng với giá trị trong lúc người dùng gõ: kiểm tra hợp lệ, định dạng, nút bị vô hiệu hóa, các trường phụ thuộc lẫn nhau. Dùng input uncontrolled khi bạn chỉ cần giá trị lúc submit, và cho input file, loại luôn là uncontrolled.