useState trong React: quản lý trạng thái component từng bước

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Props cho phép component nhận dữ liệu từ bên ngoài, nhưng giao diện tương tác cần nhớ dữ liệu thay đổi theo thời gian: số lần bấm, nội dung ô nhập, trạng thái mở hoặc đóng của menu. Đó là việc của state, và Hook useState là cách đơn giản nhất để dùng nó.

Ví dụ đầu tiên: bộ đếm

import { useState } from 'react';

function BoDem() {
  const [dem, setDem] = useState(0);

  return (
    <div>
      <p>Bạn đã bấm {dem} lần</p>
      <button onClick={() => setDem(dem + 1)}>Bấm tôi</button>
    </div>
  );
}

useState(0) trả về một mảng hai phần tử: giá trị hiện tại (dem) và hàm để cập nhật (setDem). Giá trị truyền vào là giá trị khởi đầu. Khi gọi setDem, React render lại component với giá trị mới. Điểm cốt lõi: bạn không được gán trực tiếp dem = dem + 1, vì React sẽ không biết cần vẽ lại.

Cập nhật dựa trên giá trị trước

State được cập nhật theo lô (batch) và giá trị bạn đọc được trong một lần render là "ảnh chụp" cố định. Gọi setDem(dem + 1) ba lần liên tiếp cũng chỉ tăng thêm một. Khi giá trị mới phụ thuộc giá trị cũ, hãy truyền hàm:

setDem(truoc => truoc + 1);
setDem(truoc => truoc + 1);
setDem(truoc => truoc + 1);   // tăng 3

State là object hoặc mảng: luôn tạo bản mới

React so sánh tham chiếu để biết state có đổi không. Nếu bạn sửa trực tiếp object hoặc mảng rồi truyền lại chính nó, React coi như không có gì thay đổi. Hãy dùng spread và các phương thức không làm đổi dữ liệu gốc:

const [todos, setTodos] = useState([]);

// thêm
setTodos([...todos, { id: Date.now(), ten: 'Học React', xong: false }]);

// đổi trạng thái hoàn thành
setTodos(todos.map(t => t.id === id ? { ...t, xong: !t.xong } : t));

// xóa
setTodos(todos.filter(t => t.id !== id));

// cập nhật object
const [user, setUser] = useState({ ten: '', tuoi: 0 });
setUser({ ...user, ten: 'Lan' });

Tránh các phương thức làm đổi mảng gốc như push, splice, sort trực tiếp trên state.

Ô nhập có kiểm soát (controlled input)

function FormTen() {
  const [ten, setTen] = useState('');

  return (
    <form onSubmit={e => { e.preventDefault(); alert('Chào ' + ten); }}>
      <input value={ten} onChange={e => setTen(e.target.value)} />
      <button type='submit' disabled={!ten.trim()}>Gửi</button>
    </form>
  );
}

Giá trị của ô nhập nằm trong state, mỗi lần gõ gọi onChange để cập nhật. Cách này cho bạn toàn quyền kiểm soát: kiểm tra hợp lệ, định dạng, vô hiệu hóa nút gửi khi chưa nhập. Đây là mẫu cơ bản của mọi form trong React.

Nên đặt state ở đâu?

  • Đặt state gần nơi dùng nhất có thể. Nếu chỉ một component cần, giữ nó ở đó.
  • Khi hai component anh em cùng cần một dữ liệu, nâng state lên component cha chung và truyền xuống qua props (lifting state up).
  • Đừng lưu trong state những giá trị tính được từ state khác. Ví dụ, nếu có danh sách và từ khóa tìm kiếm, danh sách đã lọc chỉ cần tính ngay trong lúc render:
const daLoc = todos.filter(t => t.ten.includes(tuKhoa));

Lỗi thường gặp

  1. Sửa state trực tiếp thay vì dùng hàm set, giao diện không cập nhật.
  2. Đọc state ngay sau khi gọi set và mong thấy giá trị mới. Giá trị mới chỉ có ở lần render kế tiếp.
  3. Gọi hook trong điều kiện hoặc vòng lặp. Hook phải được gọi ở cấp cao nhất của component, theo thứ tự cố định.
  4. Gọi hàm set trực tiếp trong thân component gây vòng lặp render vô hạn. Chỉ gọi trong sự kiện hoặc effect.
  5. Thiếu key khi render danh sách khiến state của các phần tử bị lẫn lộn.

Ví dụ: danh sách việc cần làm nhỏ

Ghép tất cả vào một component hoàn chỉnh để thấy cách chúng phối hợp:

function Todo() {
  const [ten, setTen] = useState('');
  const [todos, setTodos] = useState([]);

  function them(e) {
    e.preventDefault();
    if (!ten.trim()) return;
    setTodos([...todos, { id: Date.now(), ten, xong: false }]);
    setTen('');
  }

  return (
    <div>
      <form onSubmit={them}>
        <input value={ten} onChange={e => setTen(e.target.value)} />
        <button>Thêm</button>
      </form>
      <ul>
        {todos.map(t => (
          <li key={t.id}>{t.ten}</li>
        ))}
      </ul>
    </div>
  );
}

Có hai state độc lập: nội dung ô nhập và danh sách. Sau khi thêm, ta xóa ô nhập bằng setTen(''). Hãy tự mở rộng bằng nút xóa và ô đánh dấu hoàn thành để luyện tay.

Kết luận

useState giúp component có bộ nhớ. Hãy ghi nhớ ba quy tắc: không sửa trực tiếp, tạo bản sao mới cho object và mảng, dùng hàm cập nhật khi phụ thuộc giá trị cũ. Hãy thử làm ứng dụng todo để luyện tập.

Nếu bạn muốn học React đến mức tự xây dựng được dự án, xem thử các khóa học trên codebem.com.

author_photo
Nguyễn Đình Cường

0 comment