React函数组件中如何让事件监听函数获取useState变量的最新值(规避useEffect移除监听器失效问题)
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:
- Create a
idxRefto track the latestidxvalue:
const idxRef = React.useRef<number>(idx);
- Use
useEffectto updateidxRef.currentwheneveridxchanges:
React.useEffect(() => { idxRef.current = idx; }, [idx]);
- Replace the direct
idxreference infunc2withidxRef.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
func1andfunc2core logic—only a single line change infunc2 - It avoids the closure trap without needing to rewrite your event binding approach
- Even though the earlier
useEffectcleanup 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

