Description
In a custom hook, I forgot to wrap my setState
call in a useEffect
, which did not pose any problems prior to React 18 but now this does not work anymore. The component does not re-render if the store is changed back to its initial value. I guess this might be expected, but there was no warning whatsoever, so it was hard to find out.
So this is more like a question:
- Is that behavior a bug?
- If it is not, would it be useful to have a warning for another developer making the same mistake?
React version: 18
Steps To Reproduce
- Have a component use
useSyncExternalStore
(for example redux 8) - Call
setState
outside ofuseEffect
or event handlers (in render) - Trigger a change to the store back and forth
Link to code example: https://codesandbox.io/s/react-18-redux-8-reproduce-bug-ojdx43
The current behavior
- The component does re-render if the store value is updated to something different than its initial value
- The component does not re-render if the store is updated to its initial value again
The expected behavior
- The component does re-render if the store value is updated to something different than its initial value
- The component also does re-render if the store is updated to its initial value again
Further digging
When I tried to trace down the issue, I found that the setState
called outside useEffect
would mess up with the fiber update queue, in particular the prior pushEffect
supposedly calling updateStoreInstance
with the latest value to have the internal cached instance up to date. It does not get called at all (cancelled? overridden?) because of the unwrapped setState
.
As a consequence, that cached instance never gets updated. Any other value but the initial value hence correctly triggers a re-render, as the checkIfSnapshotChanged
ends up always comparing against that initial value.