Bài 4. State và hook

State làm cho giao diện "sống": khi dữ liệu đổi, giao diện tự cập nhật. Hook là các hàm đặc biệt để dùng state và hiệu ứng.

45 phútTrung cấp
Bạn sẽ học được
  • Dùng useState lưu trạng thái
  • Dùng useEffect cho tác vụ phụ như gọi API
  • Xử lý sự kiện và biểu mẫu có kiểm soát
  • Nâng state lên component cha
Thuật ngữ cần biết
State
Dữ liệu riêng của component; thay đổi sẽ vẽ lại giao diện.
Hook
Hàm bắt đầu bằng use cho phép dùng tính năng React trong component hàm.
Effect
Việc chạy sau khi vẽ: gọi API, đặt tiêu đề, đăng ký sự kiện.
Controlled input
Ô nhập có giá trị do state quản lý.

Khái niệm và lý thuyết

Mỗi lần component được vẽ (render), React gọi lại hàm component với dữ liệu hiện tại. Biến thường trong hàm bị tạo lại mỗi lần, vì vậy dữ liệu muốn "nhớ" qua các lần vẽ phải để trong state. Gọi hàm cập nhật (như setN) báo cho React vẽ lại component với giá trị mới. Cập nhật state là bất biến: với mảng và đối tượng phải tạo bản mới thay vì sửa tại chỗ.

Hook là hàm đặc biệt bắt đầu bằng use. useState lưu trạng thái; useEffect chạy hiệu ứng phụ (gọi API, đăng ký sự kiện, đổi tiêu đề) sau khi vẽ xong, và chạy lại khi các giá trị trong mảng phụ thuộc đổi. Mảng rỗng [] nghĩa là chỉ chạy một lần khi component xuất hiện. Hai luật của hook: chỉ gọi ở cấp cao nhất của component và chỉ gọi trong component hoặc hook khác.

Khi hai component cần chia sẻ dữ liệu, ta nâng state lên component cha chung và truyền xuống bằng props.

useState

import { useState } from "react";

function BoDem() {
  const [n, setN] = useState(0);
  return (
    <div>
      <p>Đã bấm: {n}</p>
      <button onClick={() => setN(n + 1)}>Tăng</button>
      <button onClick={() => setN(0)}>Đặt lại</button>
    </div>
  );
}

Không sửa trực tiếp n; luôn gọi hàm setN. Với đối tượng và mảng, tạo bản sao mới: setDs([...ds, moi]).

Biểu mẫu có kiểm soát

function Loc() {
  const [tu, setTu] = useState("");
  const ds = ["Python", "React", "Git"];
  const kq = ds.filter(x => x.toLowerCase().includes(tu.toLowerCase()));
  return (
    <>
      <input value={tu} onChange={e => setTu(e.target.value)} placeholder="Tìm..." />
      <ul>{kq.map(x => <li key={x}>{x}</li>)}</ul>
    </>
  );
}

useEffect gọi API

import { useState, useEffect } from "react";

function BaiViet() {
  const [ds, setDs] = useState([]);
  const [dangTai, setDangTai] = useState(true);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts?_limit=5")
      .then(r => r.json())
      .then(d => { setDs(d); setDangTai(false); });
  }, []);            // [] = chỉ chạy một lần khi hiện ra

  if (dangTai) return <p>Đang tải...</p>;
  return <ul>{ds.map(b => <li key={b.id}>{b.title}</li>)}</ul>;
}

Nâng state lên

function Cha() {
  const [mau, setMau] = useState("xanh");
  return (
    <>
      <ChonMau mau={mau} doi={setMau} />
      <HienMau mau={mau} />
    </>
  );
}

Khi hai component cần dùng chung dữ liệu, đặt state ở component cha gần nhất rồi truyền xuống.

Tình huống: Giỏ hàng nhỏ

State là mảng sản phẩm trong giỏ. Bấm "Thêm" gọi setGio([...gio, sp]); tổng tiền tính từ gio.reduce(...); giao diện tự cập nhật, không cần chạm vào DOM.

Mẹo hay

Cài React Developer Tools (tiện ích trình duyệt) để xem cây component, props và state.

Lưu ý

Không gọi hook trong điều kiện, vòng lặp hay hàm lồng; chỉ gọi ở cấp cao nhất của component. Quên mảng phụ thuộc trong useEffect dễ gây vòng lặp vô hạn.

Thực hành
  1. Làm bộ đếm có nút tăng, giảm, đặt lại.
  2. Làm ô tìm kiếm lọc danh sách khóa học.
  3. Tải danh sách bài viết từ API và hiển thị trạng thái Đang tải, Lỗi.
Tóm tắt bài học
  • State thay đổi thì giao diện cập nhật.
  • useEffect cho tác vụ phụ, mảng phụ thuộc điều khiển khi nào chạy.
  • Dữ liệu chung đặt ở component cha.