# 📦 Props và composition: truyền dữ liệu giữa component

By Better Wise

Bài trước bạn đã dựng component đầu tiên và biết JSX cho phép viết markup ngay trong JavaScript. Nhưng nếu component chỉ hiển thị đúng một nội dung cố định thì gần như không dùng lại được, bạn sẽ phải copy paste. Props là cách React giải quyết việc đó. Bài này nằm trong cùng mạch dự án "ứng dụng quản lý chi tiêu" mà bạn sẽ hoàn thiện ở module 3.

Sau bài này, bạn có thể:

- Truyền props từ component cha xuống component con, đúng kiểu dữ liệu (chuỗi, số, biến).
- Destructuring props ngay trong tham số và đặt giá trị mặc định cho prop thiếu.
- Dùng `children` để bọc nội dung, tạo ra component layout tái sử dụng.
- Nhận ra dấu hiệu nên tách component thay vì copy paste giao diện.

## Props: tham số đầu vào của component

Bạn đã quen với attribute trong HTML:

```html
<img src="avatar.png" alt="Ảnh đại diện" />
```

React component nhận dữ liệu theo đúng kiểu đó, gọi là **props** (viết tắt của properties). Khác biệt duy nhất: props do bạn tự định nghĩa.

```jsx
function ExpenseItem(props) {
  return (
    <li className="expense-item">
      <span>{props.title}</span>
      <strong>{props.amount.toLocaleString('vi-VN')} đ</strong>
    </li>
  );
}

// Dùng ở component cha
<ExpenseItem title="Cà phê sáng" amount={35000} />
<ExpenseItem title="Gửi xe" amount={5000} />
```

Hai cách truyền giá trị, đừng lẫn lộn:

| Kiểu dữ liệu | Cách viết | Ví dụ |
| --- | --- | --- |
| Chuỗi | dấu nháy | `title="Cà phê sáng"` |
| Số, biến, object, hàm | dấu ngoặc nhọn | `amount={35000}`, `item={expense}` |

Viết `amount="35000"` thì component nhận được **chuỗi**, gọi `.toLocaleString()` sẽ sai kiểu. Đây là lỗi số một của người mới.

Một điểm cần nhớ: props chỉ chảy **một chiều**, từ cha xuống con. Con không sửa được props của mình.

> **Important:** 
> Props là read-only. Viết `props.title = "Khác"` bên trong component sẽ không có tác dụng và React sẽ cảnh báo. Muốn dữ liệu thay đổi, đó là việc của state ở bài sau.

```mermaid
flowchart TD
    App["📱<br/>App (cha)"] -->|"title, amount"| Item["🧾<br/>ExpenseItem (con)"]
    App -->|"title, children"| Card["🗂️<br/>Card (bọc nội dung)"]
    Card --> Item
```

**Thử ngay 1:** Component `ExpenseItem` ở trên bị lỗi khi một khoản chi không có `amount`. Sửa bằng cách đặt `amount = 0` làm giá trị mặc định khi destructuring, và kiểm tra lại kết quả hiển thị.

**Xem gợi ý**

Đổi thành `function ExpenseItem({ title, amount = 0 })`. Với `amount = 0`, `toLocaleString` vẫn chạy và hiển thị `0 đ` thay vì crash.

## Destructuring props và giá trị mặc định

Vì props là một object, bạn dùng được destructuring đã học ở module 1:

```jsx
function ExpenseItem({ title, amount = 0, category = 'Khác' }) {
  return (
    <li className="expense-item">
      <span>{title}</span>
      <em>{category}</em>
      <strong>{amount.toLocaleString('vi-VN')} đ</strong>
    </li>
  );
}
```

Cách viết này gọn hơn `props.title`, `props.amount`, và nhìn tham số là biết component cần gì. Giá trị mặc định chỉ kích hoạt khi prop là `undefined`, tức là không truyền, hoặc truyền `undefined`. Truyền `amount={null}` hay `amount={0}` thì mặc định **không** áp dụng.

Một mẹo nhỏ: giữ danh sách props dưới 5, 6 cái. Nếu vượt qua, thường là dấu hiệu component đang làm quá nhiều việc.

**Thử ngay 2:** Thêm prop `note` vào `ExpenseItem` để hiển thị ghi chú nhỏ, với mặc định là `"Không có ghi chú"`. Sau đó render hai khoản chi: một khoản có note, một khoản không truyền note.

**Xem đáp án**

```jsx
function ExpenseItem({ title, amount = 0, note = 'Không có ghi chú' }) {
  return (
    <li className="expense-item">
      <span>{title}</span>
      <small>{note}</small>
      <strong>{amount.toLocaleString('vi-VN')} đ</strong>
    </li>
  );
}

<ExpenseItem title="Cà phê sáng" amount={35000} note="Uống với đồng nghiệp" />
<ExpenseItem title="Gửi xe" amount={5000} />
```

Khoản thứ hai hiển thị "Không có ghi chú" vì prop `note` là `undefined`.

## children: khe cắm nội dung vào component

Thẻ `<div>` trong HTML bọc được mọi thứ bên trong nó. Component React cũng vậy, phần nội dung nằm giữa thẻ mở và thẻ đóng được React gom vào một prop đặc biệt tên là `children`.

```jsx
function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </section>
  );
}

<Card title="Chi tiêu tháng 7">
  <ExpenseItem title="Cà phê sáng" amount={35000} />
  <ExpenseItem title="Gửi xe" amount={5000} />
</Card>
```

`Card` không cần biết bên trong là gì. Nó chỉ lo khung, viền, tiêu đề. Đây chính là **composition**: ghép component nhỏ vào component lớn thay vì viết một component khổng lồ biết mọi thứ.

Bạn cũng có thể truyền JSX qua prop thường, không chỉ `children`:

```jsx
<Card title="Tổng kết" actions={<button>Thêm khoản chi</button>}>
  <p>Chưa có dữ liệu</p>
</Card>
```

Bên trong `Card`, chỗ nào cần hiện nút thì render `{actions}`, chỗ nào cần nội dung thì render `{children}`.

**Thử ngay 3:** Tạo component `Badge` nhận `children` và hiển thị một nhãn nhỏ màu xám, dùng nó để gắn nhãn "Ăn uống" cạnh tên khoản chi.

**Xem đáp án**

```jsx
function Badge({ children }) {
  return <span className="badge">{children}</span>;
}

<Badge>Ăn uống</Badge>
```

`children` ở đây là chuỗi `"Ăn uống"`, vẫn render bình thường vì JSX cho phép chèn chuỗi.

## Tách component khi giao diện bắt đầu lặp lại

Giả sử bạn viết danh sách chi tiêu bằng cách copy paste:

```jsx
<li className="expense-item"><span>Cà phê sáng</span><strong>35.000 đ</strong></li>
<li className="expense-item"><span>Gửi xe</span><strong>5.000 đ</strong></li>
<li className="expense-item"><span>Ăn trưa</span><strong>45.000 đ</strong></li>
```

Ba khối giống hệt nhau, chỉ khác dữ liệu. Quy tắc thực dụng: **copy paste khối JSX lần thứ hai là lúc nên tách component**, và biến phần khác nhau thành props. Ở đây khác nhau là `title` và `amount`, nên `ExpenseItem` ra đời.

Lợi ích không chỉ là code ngắn hơn. Khi cần thêm chức năng "xóa khoản chi", bạn sửa một chỗ trong `ExpenseItem`, cả danh sách được hưởng. Ngược lại, sửa ba khối copy paste là ba lần có thể sai.

Một lưu ý về cách đặt tên: component phải viết hoa chữ đầu (`ExpenseItem`). Viết thường, React sẽ hiểu đó là thẻ HTML và render ra thẻ rỗng.

## Ôn tập nhanh: điền khuyết và flashcard

Điền vào chỗ trống để tự kiểm tra mức hiểu:

- Props truyền theo chiều _____, từ component cha xuống component _____.
- Bên trong component con, props là _____ (chỉ đọc), không được gán lại.
- Prop đặc biệt chứa nội dung nằm giữa thẻ mở và thẻ đóng là _____.
- Viết `amount="35000"` thì component nhận kiểu _____, muốn nhận số phải viết `amount={35000}`.
- Giá trị mặc định của prop chỉ có tác dụng khi prop đó là _____.

Front: Props là gì?
Back: Dữ liệu truyền từ component cha xuống component con, giống attribute của thẻ HTML.

Front: Truyền prop kiểu chuỗi trong JSX viết thế nào?
Back: Dùng dấu nháy: title="Cà phê sáng".

Front: Truyền prop kiểu số hoặc biến trong JSX viết thế nào?
Back: Đặt trong dấu ngoặc nhọn: amount={35000} hoặc item={expense}.

Front: Component con có được gán lại props không?
Back: Không. Props là read-only, gán lại sẽ không có tác dụng và React cảnh báo.

Front: Destructuring props ngay trong tham số viết thế nào?
Back: function ExpenseItem({ title, amount }) { ... }

Front: Giá trị mặc định của prop kích hoạt khi nào?
Back: Khi prop đó là undefined, tức là không truyền hoặc truyền undefined.

Front: children là gì?
Back: Prop đặc biệt chứa nội dung nằm giữa thẻ mở và thẻ đóng của component.

Front: Làm sao hiển thị children bên trong component?
Back: Đặt {children} vào đúng vị trí trong JSX trả về.

Front: Composition trong React là gì?
Back: Ghép component nhỏ vào component lớn qua props hoặc children thay vì viết một component làm mọi thứ.

Front: Có thể truyền JSX element qua prop thường không?
Back: Có, ví dụ actions={<button>Thêm</button>} rồi render {actions}.

Front: Dấu hiệu nên tách một component mới?
Back: Cùng một khối JSX bị copy paste nhiều lần và chỉ khác dữ liệu.

Front: Vì sao viết amount="35000" là lỗi tiềm ẩn?
Back: Vì đó là chuỗi, gọi các hàm số như toLocaleString hay cộng trừ sẽ sai kiểu.

## Bốn lỗi người mới hay mắc với props

1. **Quên dấu ngoặc nhọn với giá trị động.** `amount=35000` là sai cú pháp, phải là `amount={35000}`. Với chuỗi thì ngược lại, đừng bọc thêm ngoặc nhọn không cần thiết.
2. **Cố sửa props trong component con.** Nếu bạn thấy mình muốn `props.x = ...`, hãy dừng lại: dữ liệu đó nên do component cha quản lý bằng state.
3. **Truyền cả object rồi render trực tiếp.** `{expense}` với `expense` là object sẽ làm React báo lỗi. Hãy truyền từng thuộc tính, hoặc render `expense.title`.
4. **Truyền quá nhiều props xuống quá nhiều tầng.** Với hai, ba tầng thì bình thường, nhưng nếu bạn phải chuyển một prop xuyên qua component không dùng đến nó, đó là lúc cấu trúc component cần xem lại.

Mẹo cuối: đặt tên prop theo ý nghĩa nghiệp vụ (`title`, `amount`, `category`) chứ không theo kiểu hiển thị (`text1`, `color2`). Component đọc lên là hiểu.

## Chốt lại: props là dây dẫn một chiều

- Props đưa dữ liệu từ cha xuống con, và chỉ đọc ở phía con.
- Destructuring trong tham số giúp component tự mô tả dữ liệu nó cần, kèm giá trị mặc định cho trường hợp thiếu.
- `children` biến component thành cái khung linh hoạt, mở đường cho composition.
- Gặp JSX lặp lại lần thứ hai, hãy tách component và biến phần khác nhau thành props.

Bài này dừng ở dữ liệu **tĩnh**: props truyền vào rồi đứng yên. Bài tiếp theo sẽ thêm `useState` để component tự thay đổi giao diện khi người dùng bấm nút, ví dụ nút "Thêm khoản chi" thực sự làm danh sách dài ra.

---

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