I'm trying to use an event emitter with React useEffect
and useState
, but it always gets the initial state instead of the updated state. It works if I call the event handler directly, even with a setTimeout
.
If I pass the value to the useEffect()
2nd argument, it makes it work, however this causes a resubscription to the event emitter every time the value changes (which is triggered off of keystrokes).
What am I doing wrong? I've tried useState
, useRef
, useReducer
, and useCallback
, and couldn't get any working.
Here's a reproduction:
Here's a code sandbox with the same in App2
:
https://codesandbox.io/s/ww2v80ww4l
App
component has 3 different implementations - EventEmitter, pubsub-js, and setTimeout. Only setTimeout works.
To clarify my goal, I simply want the value in handleEvent
to match the Codemirror value in all cases. When any button is clicked, the current codemirror value should be displayed. Instead, the initial value is displayed.
value
is stale in the event handler because it gets its value from the closure where it was defined. Unless we re-subscribe a new event handler every time value
changes, it will not get the new value.
Solution 1: Make the second argument to the publish effect [value]
. This makes the event handler get the correct value, but also causes the effect to run again on every keystroke.
Solution 2: Use a ref
to store the latest value
in a component instance variable. Then, make an effect which does nothing but update this variable every time value
state changes. In the event handler, use the ref
, not value
.
const [value, setValue] = useState(initialValue);
const refValue = useRef(value);
useEffect(() => {
refValue.current = value;
});
const handleEvent = (msg, data) => {
console.info("Value in event handler: ", refValue.current);
};
https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often
Looks like there are some other solutions on that page which might work too. Much thanks to @Dinesh for the assistance.