I have a JS library (Cytoscape) inside my React component. The library need to be handled events with set event handler functions (on diagram/ component initilazation).
The problem is that inside these events the state is stale - it only gets the first state because of the closure.
const [otherState, setOtherState] = useState()
const handleMouseOverNode = (e) => {
// this only gets the initial value but doesn't update when state changes
console.log (otherState)
}
useEffect(() => {
cyRef.current.on('mouseover', 'node', handleMouseOverNode);
}
return () => {
cyRef.current.removeListener('mouseover');
};
}, [cyRef.current]);
How do I get the real (non stale) state ?
The experimental useEffectEvent
would solve this problem eventually. You can currently use it, but it's not stable, and might break in the future (see Declaring an Effect Event):
import { experimental_useEffectEvent as useEffectEvent } from 'react';
const [otherState, setOtherState] = useState()
// wrap with useEffectEvent
const handleMouseOverNode = useEffectEvent((e) => {
console.log(otherState)
})
useEffect(() => {
cyRef.current.on('mouseover', 'node', handleMouseOverNode);
return () => {
cyRef.current.removeListener('mouseover');
};
}, [cyRef.current]); // no need to add handleMouseOverNode as a dep
Currently the clunky solution is to use another ref to hold the updated value:
const [otherState, setOtherState] = useState()
const otherStateRef = useRef(otherState) // create a ref
// always update the ref with current value
useEffect(() => {
otherStateRef.current = otherState
})
useEffect(() => {
const handleMouseOverNode = (e) => {
console.log(otherStateRef.current) // get the value from the ref
}
cyRef.current.on('mouseover', 'node', handleMouseOverNode);
return () => {
cyRef.current.removeListener('mouseover');
};
}, [cyRef.current]);