You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React函数组件中如何让事件监听函数获取useState变量的最新值(规避useEffect移除监听器失效问题)

How to access the latest idx state in func2 with React Hooks (minimal refactoring)

Problem

I'm refactoring a React component to a function component using Hooks, and here's a snippet of my current code:

const T = () => {
  const [idx, setIdx] = React.useState<number>(0);
  // ...other complex logic
  const func1 = (e: React.MouseEvent<HTMLDivElement>, c: number) => {
    setIdx(c);
    window.addEventListener("mousemove", func2);
  }
  const func2 = (e: MouseEvent) => {
    console.log(idx); // Always logs the old value of idx, not the latest state
  }
}

func1 triggers on mouse down, and func2 is called by the global mousemove event listener during dragging. Since the component's overall logic is quite complex, I want to make as few changes as possible to the existing structure, but right now func2 can never get the updated idx value.

Side note: This question was marked as a duplicate of a "useState set method doesn't update immediately" question. However, the accepted solution using useEffect to remove event listeners didn't work for my scenario, though the first solution from that question did work (the second one didn't).

Solution

This is a classic closure trap issue: When func2 is added as an event listener, it captures the current idx state value at that moment. Even after setIdx updates the state, func2 still references the old value from the closure.

Since you want to minimize refactoring, using useRef to sync the latest idx value is the perfect fit—ref's current property is mutable and won't be trapped by old closures.

Step-by-step implementation:

  1. Create a idxRef to track the latest idx value:
const idxRef = React.useRef<number>(idx);
  1. Use useEffect to update idxRef.current whenever idx changes:
React.useEffect(() => {
  idxRef.current = idx;
}, [idx]);
  1. Replace the direct idx reference in func2 with idxRef.current:
const func2 = (e: MouseEvent) => {
  console.log(idxRef.current); // Now gets the latest idx value
}

Here's the full modified code:

const T = () => {
  const [idx, setIdx] = React.useState<number>(0);
  const idxRef = React.useRef<number>(idx);

  React.useEffect(() => {
    idxRef.current = idx;
  }, [idx]);

  // ...other complex logic
  const func1 = (e: React.MouseEvent<HTMLDivElement>, c: number) => {
    setIdx(c);
    window.addEventListener("mousemove", func2);
    // Important: Don't forget to clean up the listener when dragging ends
    const handleMouseUp = () => {
      window.removeEventListener("mousemove", func2);
      window.removeEventListener("mouseup", handleMouseUp);
    };
    window.addEventListener("mouseup", handleMouseUp);
  };

  const func2 = (e: MouseEvent) => {
    console.log(idxRef.current); // Now logs the latest idx state
  };
};

Why this works for your scenario:

  • It barely touches your existing func1 and func2 core logic—only a single line change in func2
  • It avoids the closure trap without needing to rewrite your event binding approach
  • Even though the earlier useEffect cleanup solution didn't work for you, this approach doesn't rely on that (though I still recommend adding the mouseup cleanup to prevent memory leaks)

内容的提问来源于stack exchange,提问作者matohak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 18:37:45