React 19.2 chính thức giới thiệu useEffectEvent, một hook mới giúp giải quyết triệt để vấn đề stale closure – thứ đã khiến không ít developer “đau đầu” suốt nhiều năm khi làm việc với useEffect, event listener hay subscription.
Hook này cho phép bạn viết code theo đúng mental model của React: render là render, event là event, không còn phải “lách luật” dependency array nữa.
useEffectEvent là gì?
useEffectEvent là một hook mới trong React 19.2, cho phép bạn tạo ra một event handler ổn định (stable reference) nhưng luôn truy cập được state và props mới nhất.
Nói cách khác:
Không bị stale closure
Không cần thêm dependency vào useEffect
Không cần useCallback + dependency array phức tạp
Code dễ đọc, đúng logic hơn
Stale Closure là gì?
Trước khi hiểu vì sao useEffectEvent “xịn” đến vậy, mình cần nhìn lại vấn đề cũ.
Ví dụ quen thuộc với useEffect
useEffect(() => {
const id = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(id);
}, []);
Điều gì xảy ra?
countluôn là giá trị ban đầuVì dependency array rỗng
[]Callback trong
setIntervalbị đóng băng state (stale closure)
Cách “sửa” trước đây
Thêm
countvào dependency → interval bị reset liên tụcDùng
useRef→ code khó đọcDùng
useCallback→ dependency chồng dependency
Không hợp lý, không đúng ý đồ ban đầu.
useEffectEvent giải quyết chuyện này như thế nào?
Ví dụ với useEffectEvent
import { useEffect, useEffectEvent } from "react";
function Counter({ count }) {
const logCount = useEffectEvent(() => {
console.log(count);
});
useEffect(() => {
const id = setInterval(() => {
logCount();
}, 1000);
return () => clearInterval(id);
}, []);
return null;
}
Điều gì khác biệt là gì?
logCountkhông thay đổi referenceNhưng bên trong nó luôn thấy
countmới nhấtuseEffectkhông cần dependencyKhông reset interval
Code đọc phát hiểu liền, không hack não
Mental Model mới: Reactive vs Non-reactive logic
React 19.2 chia logic thành 2 loại rõ ràng:
Reactive logic (phản ứng với render)
useEffect
dependency array
chạy lại khi props/state thay đổi
Non-reactive logic (event logic)
click
timer
websocket
analytics
subscription callback
👉 useEffectEvent dành cho loại thứ 2
So sánh: useEffectEvent vs useEffect vs useCallback
| Hook | Reference ổn định | Truy cập state mới | Dependency array | Mục đích chính |
| useEffect | ❌ | ✅ | Bắt buộc | Side effects |
| useCallback | ✅ | ❌ (nếu deps sai) | Phức tạp | Memo function |
| useEffectEvent | ✅ | ✅ | ❌ | Event logic |
👉 useEffectEvent không thay thế useEffect, mà bổ sung đúng chỗ React còn thiếu.
Use case thực tế nên dùng useEffectEvent
1. Event Listener (window, document)
const onScroll = useEffectEvent(() => {
console.log(window.scrollY);
});
useEffect(() => {
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
Không lo stale state, không cần deps.
2. WebSocket / Subscription
const onMessage = useEffectEvent((msg) => {
setMessages(prev => [...prev, msg]);
});
useEffect(() => {
socket.on("message", onMessage);
return () => socket.off("message", onMessage);
}, []);
3. Analytics / Logging
const trackClick = useEffectEvent(() => {
analytics.track("click", { userId });
});
Tracking luôn dùng userId mới nhất, không cần re-register event.
Những điều KHÔNG nên làm
Gọi useEffectEvent trong điều kiện
if (condition) {
useEffectEvent(() => {});
}
👉 Hook rule vẫn áp dụng, tuyệt đối tránh làm điều này.
Dùng useEffectEvent thay cho useEffect
// Sai mục đích
useEffectEvent(() => {
fetchData();
});
👉 useEffectEvent không chạy tự động, nó chỉ là function.
useEffectEvent và tương lai của React
useEffectEvent giúp:
Giảm eslint-disable
Ít bug do dependency
Code gần với “JS thuần” hơn
Chuẩn bị cho React Compiler & concurrent features
Nhiều khả năng trong tương lai, React sẽ khuyến khích tách rõ render logic và event logic bằng hook này.
Tổng kết
useEffectEvent không phải là một hook “cho vui”, mà là một bước tiến rất lớn trong triết lý thiết kế của React. Nó giải quyết tận gốc vấn đề stale closure, giúp code dễ đọc hơn, đúng ý đồ hơn và ít bug hơn.
Nếu bạn:
Hay viết event listener
Hay làm timer, socket, analytics
Ghét dependency array
👉 useEffectEvent chắc chắn là thứ bạn nên áp dụng ngay từ bây giờ.
Tài liệu tham khảo
React Blog: https://react.dev/blog/2025/10/01/react-19-2#use-effect-event
React Reference: https://react.dev/reference/react/useEffectEvent
