# 🔄 State với useState và xử lý sự kiện

By Better Wise

Ở bài trước, `props` giúp bạn truyền dữ liệu từ component cha xuống component con, nhưng props là **chỉ đọc**: component con không được tự sửa props. Vậy điều gì xảy ra khi người dùng bấm nút và ta cần giao diện thay đổi? Đó là lúc cần đến **state**, bộ nhớ riêng của component. Bài này là nền tảng cho hầu hết mọi thứ bạn sẽ làm với React về sau, từ form đến gọi API.

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

- Giải thích được vì sao biến JavaScript thường không làm UI cập nhật
- Khai báo và đọc state bằng `useState`
- Gắn handler cho sự kiện `click` và `change` đúng cách
- Cập nhật state dựa trên giá trị cũ và cập nhật object/array mà không mutate

## Vì sao biến thường không đủ?

Bạn đã quen với kiểu này khi viết HTML thuần:

```js
let count = 0;
button.addEventListener("click", () => {
  count = count + 1;
  label.textContent = `Bạn đã bấm ${count} lần`;
});
```

Chạy được, nhưng bạn phải tự tay tìm đúng phần tử và tự cập nhật nó. Khi giao diện lớn dần, mỗi chỗ hiển thị `count` đều phải nhớ cập nhật, quên một chỗ là UI lệch ngay.

React đảo ngược cách làm: bạn chỉ khai báo **dữ liệu**, React lo việc vẽ lại giao diện. Công thức tư duy là `UI = f(state)`. State đổi thì React render lại component.

```mermaid
flowchart TD
    A["🖱️ Người dùng click"] --> B["⚙️ Handler chạy"]
    B --> C["📦 setState với giá trị mới"]
    C --> D["🔁 React render lại component"]
    D --> E["🖥️ UI cập nhật"]
```

| | Biến thường | State |
| --- | --- | --- |
| Khi giá trị đổi | UI không đổi theo | React render lại component |
| Giữa các lần render | Bị tạo lại, mất giá trị | Được React giữ lại |
| Ai quyết định hiển thị | Bạn tự cập nhật DOM | React tự lo |

## Khai báo state với useState

`useState` là một **hook**, trả về một mảng hai phần tử. Bạn dùng destructuring (đã học ở Module 1) để lấy ra:

```jsx
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  //     ↑ đọc      ↑ cập nhật    ↑ giá trị ban đầu

  return <p>Bạn đã bấm {count} lần</p>;
}
```

Đọc state bằng biến `count`. Cập nhật state bằng cách **gọi** `setCount(giá trị mới)`, không bao giờ gán trực tiếp `count = 5`. Gán trực tiếp chỉ đổi biến trong bộ nhớ, React không biết để render lại.

> **Warning:** 
> Chỉ gọi `useState` ở **top-level** của component. Đừng gọi trong `if`, vòng lặp hay hàm lồng nhau, vì React dựa vào thứ tự gọi hook giữa các lần render.

## Gắn handler cho sự kiện click

Tên sự kiện trong React viết **camelCase** và nhận một **hàm**, không phải chuỗi: `onClick`, `onChange`, `onSubmit`.

```jsx
function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>Bạn đã bấm {count} lần</p>
      <button onClick={handleClick}>Bấm tôi</button>
    </div>
  );
}
```

Lỗi kinh điển của người mới:

```diff
- <button onClick={handleClick()}>Bấm tôi</button>
+ <button onClick={handleClick}>Bấm tôi</button>
```

Viết `handleClick()` là **gọi hàm ngay lúc render**, gây vòng lặp vô hạn vì mỗi lần gọi lại làm state đổi và render tiếp. Chỉ khi cần truyền tham số bạn mới bọc trong arrow function: `onClick={() => handleDelete(item.id)}`.

## Cập nhật state dựa trên giá trị cũ

Giả sử bạn muốn cộng 2 lần trong cùng một handler:

```jsx
// Sai: cả hai dòng đều đọc count cũ
setCount(count + 1);
setCount(count + 1);

// Đúng: dùng dạng hàm, React đưa giá trị mới nhất vào
setCount((c) => c + 1);
setCount((c) => c + 1);
```

Khi giá trị mới phụ thuộc giá trị cũ, luôn dùng _____ như `setCount(c => c + 1)`. Biến `count` trong một lần render là một **ảnh chụp** cố định, nó không tự đổi sau khi bạn gọi `setCount`.

> **Note:** 
> Gọi `setCount` không làm biến `count` đổi ngay trong hàm hiện tại. React gom các lần cập nhật rồi render lại sau khi handler chạy xong.

## Sự kiện change với input

Với `<input>`, bạn gắn `onChange` và đọc `e.target.value`. Đây là nền tảng của controlled component mà bài sau sẽ khai thác sâu hơn.

```jsx
function SearchBox() {
  const [keyword, setKeyword] = useState("");

  return (
    <>
      <input
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="Tìm việc cần làm..."
      />
      <p>Đang tìm: {keyword}</p>
    </>
  );
}
```

Cặp `value` + `onChange` khiến state là nguồn sự thật duy nhất: gõ phím làm state đổi, state đổi làm input hiển thị ký tự mới.

## Cập nhật object và array: tạo bản mới

State là bất biến (immutable). Đừng sửa trực tiếp `user.name = "An"`, hãy tạo object mới bằng spread:

```jsx
const [user, setUser] = useState({ name: "Bình", age: 20 });

setUser({ ...user, name: "An" });        // object mới, giữ các field cũ
setTodos([...todos, newTodo]);           // mảng mới, thêm phần tử
setTodos(todos.filter((t) => t.id !== id)); // mảng mới, bỏ phần tử
```

Đây chính là lý do bạn học `spread`, `filter`, `map` từ Module 1. Reactive không so sánh từng field, nó so sánh **tham chiếu**: nếu bạn mutate object cũ, tham chiếu không đổi và React có thể bỏ qua lần cập nhật.

### Bài tập nhanh 1

Trong dự án Vite của bạn, tạo component `LikeButton`. Mỗi lần bấm, số lượt thích tăng 1 và nút đổi chữ thành "Đã thích" khi `likes > 0`. Gợi ý: bạn cần hai state hay chỉ một? Hãy thử suy ra "đã thích" từ `likes` trước khi thêm state thứ hai.

**Xem gợi ý**

Chỉ cần một state `likes`. Trạng thái "đã thích" là **giá trị dẫn xuất**: `const liked = likes > 0;`. Thêm state thứ hai cho cùng một thông tin là lỗi phổ biến khiến hai state lệch nhau.

### Bài tập nhanh 2

Thêm vào `TodoApp` của bạn một ô input và một state `draft`. Khi bấm "Thêm", đưa `draft` vào mảng `todos` và xóa trắng input.

**Xem gợi ý**

Dùng `setTodos([...todos, { id: Date.now(), text: draft }])` rồi `setDraft("")`. Nếu bạn viết `todos.push(...)` thì mảng không đổi tham chiếu và UI sẽ không cập nhật.

## Những bẫy thường gặp khi làm việc với state

- **Gán trực tiếp state**: `count = count + 1` không kích hoạt render. Luôn gọi hàm setter.
- **Gọi handler ngay**: `onClick={handleClick()}` chạy lúc render. Bỏ cặp ngoặc đơn.
- **Quên dạng hàm khi cộng dồn**: nhiều lệnh `setX(x + 1)` liên tiếp chỉ tính một lần.
- **Mutate object/array**: luôn tạo bản mới bằng spread hoặc `map`/`filter`.
- **Lưu giá trị dẫn xuất vào state**: nếu tính được từ state khác thì đừng lưu, hãy tính trực tiếp khi render.
- **Đặt state quá cao**: state chỉ nên sống ở component gần nhất cần dùng nó. Đẩy lên quá cao làm mọi thứ render lại không cần thiết.

Quy tắc vàng: **một state cho một sự thật, và mọi thứ khác hãy tính ra từ nó**.

## Tóm tắt nhanh và bài kế tiếp

- State là bộ nhớ của component; đổi state thì React render lại.
- `const [value, setValue] = useState(initial)` là cú pháp chuẩn, gọi ở top-level.
- Sự kiện React dùng camelCase và nhận **hàm**: `onClick={fn}`, `onChange={(e) => ...}`.
- Cập nhật dựa trên giá trị cũ thì dùng `setX(c => c + 1)`.
- Object và array phải được thay bằng bản mới, không mutate.

Bài tiếp theo, "Render danh sách và key trong React", sẽ dùng chính những kiến thức này để biến mảng `todos` thành danh sách phần tử và giải thích vì sao `key` lại quan trọng đến vậy.

Trong React, state là _____ quyết định giao diện, đổi state thì component sẽ _____.
Cú pháp khai báo là const [value, setValue] = _____(giá trị ban đầu).
Khi cần truyền tham số cho handler, ta bọc nó trong một _____.
Với object, ta cập nhật bằng cách tạo bản mới qua _____ thay vì sửa trực tiếp.

Front: State là gì?
Back: Bộ nhớ riêng của component; khi state đổi, React render lại component đó.

Front: Cú pháp khai báo state với useState?
Back: const [value, setValue] = useState(giaTriBanDau);

Front: Vì sao không được gán trực tiếp count = count + 1?
Back: Gán trực tiếp không gọi setter nên React không biết để render lại.

Front: onClick nhận hàm hay chuỗi?
Back: Nhận một hàm, ví dụ onClick={handleClick}, không phải chuỗi.

Front: Lỗi khi viết onClick={handleClick()} là gì?
Back: Hàm bị gọi ngay lúc render, có thể gây vòng lặp render vô hạn.

Front: Khi nào dùng setCount(c => c + 1)?
Back: Khi giá trị mới phụ thuộc giá trị cũ, để luôn nhận giá trị mới nhất.

Front: Hai lệnh setCount(count + 1) liên tiếp cộng mấy lần?
Back: Chỉ một lần, vì cả hai đều đọc cùng một ảnh chụp count cũ.

Front: Đọc giá trị input trong onChange thế nào?
Back: Dùng e.target.value, ví dụ setKeyword(e.target.value).

Front: Cặp value + onChange của input tạo ra gì?
Back: Controlled component: state là nguồn sự thật duy nhất của input.

Front: Vì sao phải tạo object mới khi cập nhật state object?
Back: React so sánh tham chiếu; mutate object cũ có thể khiến lần cập nhật bị bỏ qua.

Front: Cách thêm phần tử vào state mảng?
Back: Tạo mảng mới: setTodos([...todos, newTodo]).

Front: Cách thêm state? Có nên lưu giá trị dẫn xuất?
Back: Không. Nếu tính được từ state khác (như liked = likes > 0) thì tính khi render.

Front: useState được gọi ở đâu?
Back: Top-level của component, không gọi trong if, vòng lặp hay hàm lồng nhau.

Front: Tên sự kiện trong React viết kiểu gì?
Back: camelCase: onClick, onChange, onSubmit.

---

## 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) — this lesson
  - [📋 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)
  - [⚙️ 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)
