# 📝 Form và controlled components

By Better Wise

## Vì sao form là chỗ dễ sai nhất khi mới học React

Ba module trước bạn đã quen với `useState` và xử lý sự kiện: bấm nút thì `count` tăng, React render lại. Form là bài kiểm tra thật sự cho tư duy đó, vì người dùng gõ từng ký tự, và React phải là nơi giữ giá trị thay vì DOM.

Sau bài này bạn sẽ:

- Chuyển một input HTML quen thuộc thành controlled input.
- Quản lý nhiều trường trong một object state duy nhất.
- Xử lý `submit` đúng cách bằng `preventDefault`.
- Validate cơ bản và hiển thị lỗi dưới từng trường.

## Từ input HTML quen thuộc đến controlled component

Bạn đã viết thế này nhiều lần:

```html
<input type="text" placeholder="Tên ghi chú" />
```

Trình duyệt tự giữ giá trị trong DOM, React không biết gì cả. Đó là **uncontrolled**. Muốn React làm chủ, ta nối hai chiều lại:

```jsx
const [title, setTitle] = useState("");

<input
  value={title}
  onChange={(e) => setTitle(e.target.value)}
/>
```

Một input là controlled khi giá trị của nó đến từ _____ và thay đổi qua sự kiện _____. Vòng lặp này chạy liên tục mỗi lần gõ:

```mermaid
flowchart TD
  A["⌨️ Người dùng gõ"] --> B["onChange chạy"]
  B --> C["setTitle với giá trị mới"]
  C --> D["React render lại"]
  D --> E["input nhận value mới"]
  E --> A
```

| | Input HTML thường | Controlled input |
| --- | --- | --- |
| Ai giữ giá trị | DOM | State của React |
| Đọc giá trị | `document.querySelector` | Biến state |
| Validate khi gõ | Khó | Dễ, chỉ cần `if` |

Vì sao phải làm khổ vậy? Vì khi giá trị nằm trong state, bạn có thể validate ngay, disable nút khi chưa hợp lệ, hay cho hai trường phụ thuộc nhau. Đó chính là lý do form trong React gần như luôn là controlled.

> **Note:** 
> Nếu `value` ban đầu là `undefined`, React sẽ cảnh báo "changing an uncontrolled input to be controlled". Luôn khởi tạo bằng chuỗi rỗng `useState("")`.

**Bài tập nhỏ:** thêm một input `email` vào form ghi chú và biến nó thành controlled.

**Xem gợi ý**

```jsx
const [email, setEmail] = useState("");

<input
  type="email"
  value={email}
  onChange={(e) => setEmail(e.target.value)}
/>
```

## Quản lý nhiều trường bằng một object state

Mỗi trường một `useState` thì form 5 trường thành 10 dòng khai báo. Cách gọn hơn là gom vào một object:

```jsx
const [form, setForm] = useState({
  title: "",
  category: "hoc-tap",
  important: false,
});

function handleChange(event) {
  const { name, value, type, checked } = event.target;
  setForm((prev) => ({
    ...prev,
    [name]: type === "checkbox" ? checked : value,
  }));
}
```

Ba điểm cần nhớ:

- Mỗi input phải có `name` khớp với key trong state.
- `[name]` là computed property, nhờ đó một handler dùng cho mọi trường.
- Với checkbox, đọc _____ thay vì `value`.
- Khi cập nhật một trường, dùng _____ để giữ các trường còn lại.

Và hai cái bẫy HTML quen thuộc:

```jsx
// textarea: dùng value, không dùng children
<textarea value={form.title} name="title" onChange={handleChange} />

// select: đặt value trên select, không đặt selected trên option
<select value={form.category} name="category" onChange={handleChange}>
  <option value="hoc-tap">Học tập</option>
  <option value="ca-nhan">Cá nhân</option>
</select>
```

**Bài tập nhỏ:** thêm trường checkbox "quan trọng" và in ra giá trị trong `console.log`.

**Xem gợi ý**

```jsx
<input
  type="checkbox"
  name="important"
  checked={form.important}
  onChange={handleChange}
/>
```

Handler chung đã xử lý `type === "checkbox"` nên không cần viết thêm gì.

## Xử lý submit đúng cách

Trong HTML thuần, bấm nút submit là trình duyệt gửi form và **tải lại trang**. Trong React, việc tải lại đó xóa sạch state, nên ta chặn nó:

```jsx
function handleSubmit(event) {
  event.preventDefault();

  const errors = validate(form);
  if (Object.keys(errors).length > 0) {
    setErrors(errors);
    return;
  }

  onAdd(form);          // báo cho component cha
  setForm({ title: "", category: "hoc-tap", important: false });
}
```

Gọi _____ trong `handleSubmit` là bước không được quên. Nút submit cần `type="submit"` để form kích hoạt `onSubmit`; nút phụ (như "Hủy") nên để `type="button"`, nếu không nó cũng submit.

## Validate cơ bản ngay khi nhập

Validate trong React rất nhẹ: một hàm thuần nhận `form` và trả về object lỗi.

```jsx
function validate(values) {
  const errors = {};
  if (values.title.trim().length < 3) {
    errors.title = "Tên ghi chú cần ít nhất 3 ký tự";
  }
  return errors;
}
```

Object lỗi rỗng nghĩa là hợp lệ, đó là toàn bộ giao kèo. Cách hiển thị:

```jsx
{errors.title && <p className="error">{errors.title}</p>}
```

Một mẹo nhỏ về trải nghiệm: chỉ hiện lỗi sau khi người dùng đã gõ hoặc đã bấm submit, đừng hiện lỗi đỏ ngay khi form vừa mở.

**Bài tập nhỏ:** bắt buộc người dùng chọn `category`, hiện lỗi dưới select nếu để trống.

**Xem gợi ý**

Đổi option đầu thành `<option value="">Chọn danh mục</option>`, thêm vào `validate`:

```jsx
if (!values.category) {
  errors.category = "Vui lòng chọn danh mục";
}
```

Rồi render `{errors.category && <p className="error">{errors.category}</p>}` ngay dưới select.

Front: Controlled component là gì?
Back: Component mà giá trị hiển thị của input do state React quyết định và cập nhật qua onChange.

Front: Hai props bắt buộc để một input thành controlled?
Back: value và onChange.

Front: Vì sao form React bị tải lại trang khi submit?
Back: Đó là hành vi mặc định của trình duyệt, cần gọi event.preventDefault().

Front: Khi nào React cảnh báo chuyển từ uncontrolled sang controlled?
Back: Khi value ban đầu là undefined rồi sau đó thành chuỗi, nên khởi tạo state bằng "".

Front: Vì sao không được gán form.title = 'abc' trực tiếp?
Back: Đó là mutate state, React không render lại; phải dùng setForm với object mới.

Front: Một handler dùng cho nhiều input lấy tên trường từ đâu?
Back: Từ event.target.name, dùng computed property [name].

Front: Với checkbox, đọc giá trị ở đâu?
Back: event.target.checked, là boolean, không phải value.

Front: Textarea trong React nhận nội dung qua đâu?
Back: Qua prop value, không dùng children như HTML thuần.

Front: Value của select đặt ở đâu trong React?
Back: Trên thẻ select, không đặt selected trên từng option.

Front: Làm sao cập nhật một trường mà giữ nguyên các trường khác?
Back: setForm(prev => ({ ...prev, [name]: value })).

Front: Hàm validate nên trả về gì?
Back: Một object lỗi với key là tên trường; object rỗng nghĩa là hợp lệ.

Front: Khi nào nên dùng controlled thay vì uncontrolled?
Back: Khi cần validate ngay, disable nút theo giá trị, hoặc các trường phụ thuộc nhau.

## Bốn lỗi form kinh điển của người mới

- **Quên `name`**: handler chung chạy nhưng `[name]` thành `undefined`, state không đổi.
- **Quên `preventDefault`**: trang tải lại, bạn tưởng state bị mất.
- **Đặt `selected` trên option hoặc viết `<textarea>{value}</textarea>`**: React cảnh báo, hãy dùng `value` trên `select` và trên `textarea`.
- **Validate chỉ bằng cách disable nút**: người dùng vẫn submit được bằng Enter; luôn validate lại trong `handleSubmit`.

Một quy tắc nhanh: nếu bạn thấy mình đang dùng `document.querySelector` hay `ref` để đọc giá trị input, hãy dừng lại và hỏi giá trị đó nên nằm trong state nào.

## Ghi nhớ nhanh và bước tiếp theo

- Controlled input = `value` từ state + `onChange` cập nhật state.
- Gom nhiều trường vào một object, dùng `name` và spread để cập nhật gọn.
- `event.preventDefault()` trong `onSubmit`, validate bằng một hàm thuần trả về object lỗi.
- Checkbox dùng `checked`, textarea và select dùng `value`.

Form của bạn giờ đã có dữ liệu sạch trong state. Bài tiếp theo, **useEffect và side effects**, sẽ trả lời câu hỏi: làm sao chạy code sau khi component render, và làm sao gọi API đúng lúc thay vì gọi lung tung mỗi lần gõ phím.

---

## Contents

Part of [⚛️ React từ con số 0 cho người chỉ biết HTML](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html.md)

- **🧱 Nền tảng JavaScript và tư duy React**
  - [✨ ES6 thiết yếu cho React: let, const, arrow function, destructuring, spread](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/es6-thiet-yeu-cho-react.md)
  - [🔁 Hàm và mảng trong thực tế: map, filter, reduce](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/ham-mang-map-filter-reduce.md) (sign-in required)
  - [🧩 Tư duy component và khởi tạo dự án với Vite](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/tu-duy-component-va-khoi-tao-vite.md)
  - [✅ Checkpoint: nền tảng JavaScript và tư duy React](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/checkpoint-nen-tang-javascript-va-react.md)
- **🧩 Component, JSX, props và state**
  - [📄 JSX và component đầu tiên](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/jsx-va-component-dau-tien.md)
  - [📦 Props và composition: truyền dữ liệu giữa component](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/props-va-composition.md)
  - [🔄 State với useState và xử lý sự kiện](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/state-usestate-va-su-kien.md)
  - [📋 Render danh sách và key trong React](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/render-danh-sach-va-key.md)
  - [✅ Checkpoint: component, JSX, props và state](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/checkpoint-component-va-state.md)
- **🚀 Form, useEffect, gọi API và dự án nhỏ**
  - [📝 Form và controlled components](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/form-va-controlled-components.md) — this lesson
  - [⚙️ useEffect và side effects](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/useeffect-va-side-effects.md)
  - [🌐 Gọi API với fetch: loading, error và cleanup](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/goi-api-voi-fetch.md) (premium content)
  - [🛠️ Dự án: ứng dụng quản lý nhỏ với Vite](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/du-an-ung-dung-quan-ly-voi-vite.md) (premium content)
  - [✅ Checkpoint: form, useEffect, gọi API và dự án](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/checkpoint-form-api-va-du-an.md) (premium content)
- [Course cheatsheet](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/cheatsheet.md) (premium content)
