# 📋 Render danh sách và key trong React

By Better Wise

Bài trước bạn đã biết state là bộ nhớ của component: đổi state thì React render lại. Nhưng dữ liệu thật hiếm khi chỉ có một giá trị, nó thường là một mảng công việc, một mảng sản phẩm, một mảng tin nhắn. Hôm nay bạn học cách biến mảng đó thành danh sách JSX, và cách chọn `key` để React cập nhật danh sách đúng.

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

- Render một mảng bằng `map()` trong JSX mà không cần vòng lặp.
- Giải thích được `key` là gì và React dùng nó để làm gì.
- Chọn được `key` ổn định từ dữ liệu thật (thường là `id`).
- Nhận ra vì sao dùng index làm `key` lại gây lỗi khó hiểu.

## Từ danh sách HTML quen thuộc đến `map` trong JSX

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

```html
<ul>
  <li>Táo</li>
  <li>Cam</li>
  <li>Xoài</li>
</ul>
```

Nhưng nếu danh sách đến từ một mảng, bạn không thể viết tay từng `<li>`. Trong JSX, dấu `{}` cho phép đặt **biểu thức JavaScript**, và `map()` là công cụ đúng để biến mảng dữ liệu thành mảng element:

```jsx
const fruits = ["Táo", "Cam", "Xoài"];

function FruitList() {
  return (
    <ul>
      {fruits.map((fruit) => (
        <li key={fruit}>{fruit}</li>
      ))}
    </ul>
  );
}
```

Đọc theo kiểu đời thường: với mỗi quả trong giỏ, tạo một thẻ `<li>` dán nhãn tên quả đó. `map()` trả về một mảng element, và JSX render mảng đó thành nhiều phần tử liền nhau.

Nếu bạn viết `{fruits.map((fruit) => { <li>{fruit}</li> })}` thì sẽ nhận về mảng `undefined`, vì mũi tên có `{}` nghĩa là thân hàm, không tự trả về gì. Dùng `()` hoặc thêm `return`.

```mermaid
flowchart TD
    Data["📦<br/>Mảng dữ liệu"] --> Map["🔁<br/>map()"]
    Map --> Elements["🧩<br/>Mảng element"]
    Elements --> React["⚛️<br/>React render"]
    React --> Key["🔑<br/>So khớp theo key"]
```

**Bài tập 1.** Trong ứng dụng quản lý công việc, bạn có `const tasks = [{ id: 1, title: "Học map" }, { id: 2, title: "Làm bài tập" }]`. Hãy render thành danh sách `<ul>`.

**Xem gợi ý**

```jsx
<ul>
  {tasks.map((task) => (
    <li key={task.id}>{task.title}</li>
  ))}
</ul>
```

Chú ý hai điểm: dùng `task.id` cho `key`, và hiển thị `task.title` chứ không phải cả object (React không render object trực tiếp).

## Key: tấm thẻ tên để React nhận diện từng phần tử

Khi danh sách thay đổi (thêm, xóa, sắp xếp lại), React cần biết phần tử nào **giữ nguyên**, phần tử nào **mới**, phần tử nào **đã mất**. Nó so khớp danh sách cũ với danh sách mới, và `key` chính là căn cứ so khớp đó. Hãy nghĩ tới phòng tập có tủ locker: nếu mọi tủ đều không ghi số, người giữ đồ phải mở từng tủ để đoán; có số tủ thì mọi thứ rõ ràng ngay.

Ba quy tắc cần nhớ:

- `key` phải **duy nhất trong danh sách anh em cùng cấp**, không cần duy nhất trong toàn app.
- `key` phải **ổn định**: cùng một bản ghi thì cùng một key qua các lần render.
- `key` **không phải prop**: component con không đọc được `key` trong `props`. Nếu cần dùng giá trị đó bên trong, truyền thêm prop riêng như `id={task.id}`.

Điền nhanh để kiểm tra trí nhớ: React yêu cầu mỗi phần tử sinh ra từ mảng có một _____ duy nhất; với dữ liệu lấy từ API, nguồn key tốt nhất là _____ của bản ghi; và `key` không được truyền vào component con như một _____.

## Chọn key ổn định từ dữ liệu thật

Thứ tự ưu tiên khi chọn `key`:

1. `id` từ database hoặc từ API, tốt nhất.
2. Một trường tự nhiên duy nhất, ví dụ `email` hay `slug`.
3. Ghép nhiều trường nếu chưa có id, ví dụ `` `${task.title}-${task.createdAt}` ``.
4. Index, chỉ dùng tạm và chỉ khi thật sự an toàn (xem phần sau).

Đừng dùng `Math.random()` hay `Date.now()`: mỗi lần render key lại đổi, React tưởng toàn bộ danh sách là mới, phá hủy rồi tạo lại các element, mất state bên trong và chậm hơn hẳn.

Một lỗi vị trí rất hay gặp: đặt `key` sai chỗ. `key` phải nằm trên element **trực tiếp được trả về** trong `map`, không phải trên thẻ bao ngoài, không phải bên trong component con.

```jsx
// Sai: key nằm trên thẻ bọc ngoài, còn element trong map thì không có
{items.map((item) => (
  <div key={item.id}>
    <ItemRow />
  </div>
))}

// Đúng: phần tử được map trực tiếp có key
{items.map((item) => (
  <ItemRow key={item.id} item={item} />
))}
```

**Bài tập 2.** Danh sách chi tiêu của bạn chưa có `id` từ server, mỗi bản ghi có `name`, `amount` và `createdAt`. Bạn sẽ chọn `key` thế nào và vì sao?

**Xem gợi ý**

Ghép hai trường: `key={`${item.name}-${item.createdAt}`}`. Cách này ổn định qua các lần render và không phụ thuộc vị trí. Khi có `id` thật từ server, hãy chuyển sang dùng `id`. Tránh dùng `amount` một mình vì hai khoản chi có thể cùng số tiền.

## Vì sao index làm key lại nguy hiểm

Dùng index làm key trông rất tiện: `items.map((item, index) => <Row key={index} />)`. Vấn đề xuất hiện khi danh sách **thay đổi thứ tự hoặc độ dài**.

Hãy tưởng tượng một danh sách công việc, mỗi dòng có một ô input để ghi chú, và ô input đó giữ state riêng. Bạn xóa công việc đầu tiên. Với index làm key, phần tử ở vị trí 0 vẫn là "key 0", nên React giữ nguyên component ở vị trí 0 và chỉ thay props. Kết quả: ghi chú của công việc đã xóa lại "nhảy" sang công việc kế tiếp. Nhìn thì dữ liệu đúng mà trải nghiệm sai, đây là kiểu bug khiến người mới mất hàng giờ.

Tóm lại, khi dùng index làm key và _____ danh sách, React có thể gán nhầm state của phần tử cũ cho phần tử mới. Index chỉ tạm chấp nhận khi danh sách là _____, không đổi thứ tự, không thêm bớt và không có state bên trong.

## Những cái bẫy khi render danh sách

- Thiếu `key` thì React cảnh báo trong console, và cảnh báo này không nên bỏ qua.
- `key` bị trùng trong cùng một danh sách cũng gây cảnh báo và render sai.
- Trả về `null` hoặc `false` cho phần tử không cần hiển thị thì hợp lệ, nhưng đừng quên `return` khi dùng `map` có `{}`.
- Đừng đặt `key` lên cả hai chỗ (thẻ ngoài và component con), chỉ cần một chỗ đúng.
- Render mảng chuỗi thì `key` vẫn cần, ví dụ `tags.map((tag) => <span key={tag}>{tag}</span>)`, miễn là chuỗi không trùng.
- Muốn render theo điều kiện, `filter()` trước rồi mới `map()`, cách này dễ đọc hơn nhét `if` vào trong JSX.

## Ôn nhanh bằng thẻ ghi nhớ

Front: Cách thường dùng để render một mảng dữ liệu thành danh sách trong JSX là gì?
Back: Gọi `map()` trên mảng và trả về JSX cho mỗi phần tử.

Front: `map()` trả về gì?
Back: Một mảng mới cùng độ dài, mỗi phần tử là kết quả của hàm callback.

Front: Vì sao React cảnh báo 'Each child in a list should have a unique key prop'?
Back: Vì các element sinh ra từ mảng đang thiếu prop `key`.

Front: `key` giúp React làm gì khi render lại?
Back: Nhận diện phần tử nào giữ nguyên, thêm mới hay bị xóa để cập nhật đúng chỗ.

Front: `key` phải duy nhất trong phạm vi nào?
Back: Trong danh sách anh em cùng cấp, không cần duy nhất toàn app.

Front: Component con có đọc được `key` qua props không?
Back: Không, React giữ `key` riêng và không truyền nó vào props.

Front: Nguồn `key` tốt nhất cho dữ liệu từ API là gì?
Back: `id` của bản ghi, vì ổn định và duy nhất.

Front: Vì sao không nên dùng index làm key khi danh sách có thể chèn, xóa hay sắp xếp lại?
Back: Index đổi theo vị trí nên React có thể gán nhầm state/DOM của phần tử cũ cho phần tử mới.

Front: Khi nào dùng index làm key tạm chấp nhận được?
Back: Danh sách tĩnh, không sắp xếp lại, không chèn/xóa và không có state bên trong.

Front: Vì sao không dùng `Math.random()` làm key?
Back: Key đổi mỗi lần render, React phá hủy và tạo lại toàn bộ phần tử.

Front: `key` phải đặt ở đâu?
Back: Trên element trực tiếp được trả về trong `map`, không phải thẻ bao ngoài.

Front: Trong `map` dùng mũi tên có `{}` thì cần nhớ gì?
Back: Phải `return` JSX, nếu không sẽ nhận về mảng `undefined`.

Front: Hai phần tử trong cùng danh sách có key trùng nhau thì sao?
Back: React cảnh báo và có thể render hoặc cập nhật sai phần tử.

## Mang gì sang bài checkpoint

- Danh sách trong JSX = `map()` + một `key` ổn định cho mỗi phần tử.
- `key` là định danh nội bộ của React, không phải prop, và chỉ cần duy nhất trong danh sách anh em.
- Ưu tiên `id` từ dữ liệu; index chỉ dùng cho danh sách tĩnh, an toàn.
- Thay đổi thứ tự hay độ dài danh sách là lúc lỗi index-key lộ ra rõ nhất.

Bài tiếp theo là checkpoint của module: bạn sẽ ghép JSX, props, state, xử lý sự kiện và render danh sách có key thành một màn hình quản lý hoàn chỉnh.

---

## 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) — this lesson
  - [✅ 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)
