I’m still getting my head around react hooks but struggling to see what I’m doing wrong here. I have a component for resizing panels, onmousedown
of an edge I update a value on state then have an event handler for mousemove
which uses this value however it doesn’t seem to be updating after the value has changed.
Here is my code:
export default memo(() => { const [activePoint, setActivePoint] = useState(null); // initial is null const handleResize = () => { console.log(activePoint); // is null but should be 'top|bottom|left|right' }; const resizerMouseDown = (e, point) => { setActivePoint(point); // setting state as 'top|bottom|left|right' window.addEventListener('mousemove', handleResize); window.addEventListener('mouseup', cleanup); // removed for clarity }; return ( <div className="interfaceResizeHandler"> {resizePoints.map(point => ( <div key={ point } className={ `interfaceResizeHandler__resizer interfaceResizeHandler__resizer--${ point }` } onMouseDown={ e => resizerMouseDown(e, point) } /> ))} </div> ); });
The problem is with the handleResize
function, this should be using the latest version of activePoint
which would be a string top|left|bottom|right
but instead is null
.
Advertisement
Answer
How to Fix a Stale useState
Currently, your issue is that you’re reading a value from the past. When you define handleResize
it belongs to that render, therefore, when you rerender, nothing happens to the event listener so it still reads the old value from its render.
There are a several ways to solve this. First let’s look at the most simple solution.
Create your function in scope
Your event listener for the mouse down event passes the point
value to your resizerMouseDown
function. That value is the same value that you set your activePoint
to, so you can move the definition of your handleResize
function into resizerMouseDown
and console.log(point)
. Because this solution is so simple, it cannot account for situations where you need to access your state outside of resizerMouseDown
in another context.
See the in-scope function solution live on CodeSandbox.
useRef
to read a future value
A more versatile solution would be to create a useRef
that you update whenever activePoint
changes so that you can read the current value from any stale context.
const [activePoint, _setActivePoint] = React.useState(null); // Create a ref const activePointRef = React.useRef(activePoint); // And create our custom function in place of the original setActivePoint function setActivePoint(point) { activePointRef.current = point; // Updates the ref _setActivePoint(point); } function handleResize() { // Now you'll have access to the up-to-date activePoint when you read from activePointRef.current in a stale context console.log(activePointRef.current); } function resizerMouseDown(event, point) { /* Truncated */ }
See the useRef
solution live on CodeSandbox.
Addendum
It should be noted that these are not the only ways to solve this problem, but these are my preferred methods because the logic is more clear to me despite some of the solutions being longer than other solutions offered. Please use whichever solution you and your team best understand and find to best meet your specific needs; don’t forget to document what your code does though.