I have an issue where handlers subscribed to a custom event see all the updated state (from calls to setSomething()
) except the last one that was set. Specifically:
const [counter1, setCounter1] = useState(1);
const [counter2, setCounter2] = useState(1);
const increment = useCallback(async () => {
events.emit('beforeIncrement');
await someAsyncAction(); // <-- There's an async call.
setCounter1((x: number) => x + 1);
setCounter2((x: number) => x + 1);
// React 17:
// Handlers of the following event will see counter1 updated value but not counter2.
// React 18 with Automatic Batching:
// Handlers of the following event will not see see counter1 or counter2 updated value.
// What is the best way to emit an event here and ensure that handlers see the updated state?
// setTimeout(() => events.emit('afterIncrement')) ??
events.emit('afterIncrement');
}, []);
The following repo demonstrates this behavior:
https://github.com/cmermingas/event-emitter-react-17
- Why is this happening?
- What is the right way to emit an event to ensure that handlers have access to all the updated state?
Thanks in advance!
[edit]
The example I provided is a simplified version of the real situation. A more generic description would be:
const doWork = useCallback(async () => {
events.emit('beforeWork');
// Asynchronous calls where React state gets updated.
events.emit('afterWork');
}, []);
The Asynchronous calls
section triggers state updates, possibly outside of the body of this callback.
I want all afterWork
handlers to have the most up-to-date state from React. I imagine an API like this:
const doWork = useCallback(async () => {
events.emit('beforeWork');
// Asynchronous calls / Promise chains here
// where React state gets updated.
// Work is done. When React has updated, emit an event:
afterReactWork(() => events.emit('afterWork'));
}, []);
Top comments (3)
it's because of asynchronicity.
if you put
await someAsyncAction();
after second setCounters it will work as you expect.
The best way is to use useEffect with dependencies.
detect both values have changed and then emit the event
stackoverflow.com/questions/629747...
Thank you for your reply and the link to SO. I updated my question with a bit more detail.
If you don't mind, I'll quote your suggestions and respond:
If there's no asynchronous call, then the
afterIncrement
handlers don't see any updated values:Here, I would still want to emit
afterWork
at the end ofdoWork
, decoupled with React's updating ofA
andB
, and I would want the handlers to have access to React's updated state.Thank you for the suggestion.
Moving the async action is not an option in the real situation because it is more complicated. However, to understand better, I extended the example with an additional counter:
Now, the
afterIncrement
handlers see the updated value forcounter1
andcounter2
but notcounter3
. **Why is it that only the last one is not updated?This will not be the best way for me because I don't want the event to be coupled with the state. Instead, I want it to be coupled with the fact that
increment
is done. I appreciate the suggestion.“Currently (React 16 and earlier), only updates inside React event handlers are batched by default” , according to Dan Abramov.
increment method is async, it should be a normal function