👋 Chào mừng đến với Blog - Thành Nam Nguyễn

useEffectEvent trong React 19

useEffectEvent trong React 19

Web Development

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?

  • count luôn là giá trị ban đầu

  • Vì dependency array rỗng []

  • Callback trong setInterval bị đóng băng state (stale closure)

Cách “sửa” trước đây

  • Thêm count vào dependency → interval bị reset liên tục

  • Dùng useRef → code khó đọc

  • Dù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ì?

  • logCount không thay đổi reference

  • Nhưng bên trong nó luôn thấy count mới nhất

  • useEffect không cần dependency

  • Khô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

HookReference ổn địnhTruy cập state mớiDependency arrayMục đích chính
useEffectBắt buộcSide effects
useCallback❌ (nếu deps sai)Phức tạpMemo function
useEffectEventEvent 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