useState trả về một cặp: giá trị hiện tại và hàm để đổi nó. Gọi hàm setter không sửa biến tại chỗ — nó lên lịch cho React render lại component với giá trị mới.
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Đếm: {count}</p>
<button onClick={() => setCount(count + 1)}>+1 trực tiếp</button>
<button onClick={() => setCount((prev) => prev + 1)}>+1 bằng updater</button>
</div>
)
}Điểm khiến người mới vấp: count là hằng số trong lần render đó.
- Nó không đổi giá trị sau khi gọi
setCount; lần render tiếp theo mới có biếncountmới. - Vì vậy đoạn này chỉ tăng 1, không tăng 3:
setCount(count + 1) // count đang là 0 → đặt lịch thành 1
setCount(count + 1) // count VẪN là 0 → đặt lịch thành 1
setCount(count + 1) // vẫn 1Dạng updater setCount((prev) => prev + 1) chữa được vì React đưa giá trị mới nhất trong hàng đợi vào prev. Ba lần gọi liên tiếp cho ra 3. Quy tắc thực dụng: khi giá trị mới tính từ giá trị cũ, dùng updater.
Vài chi tiết đáng nhớ. Đối số của useState chỉ dùng ở lần render đầu, các lần sau bị bỏ qua. Nếu giá trị khởi tạo tốn công tính, truyền hàm thay vì giá trị: useState(() => parse(raw)) — bản useState(parse(raw)) vẫn chạy parse mỗi lần render dù kết quả bị vứt đi.
Lưu ý: React bỏ qua re-render khi giá trị mới bằng giá trị cũ theo Object.is. Nên setItems(items) sau khi items.push(x) sẽ không làm gì cả — vẫn là cùng một tham chiếu mảng. Phải tạo mảng mới: setItems([...items, x]).