-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: useSyncExternalStore does not update internal value if a setState is also called, outside useEffect, causing store sets not re-render #25565
Comments
In order to familiarize myself with react codebase, I wrote a small test that fails on this pattern. (even if it is expected so) Here is it fwiw (in test('store value is correctly stored in current hook instance even with interleaved effects occurring', async () => {
const store = createExternalStore('value:initial');
function App() {
const value = useSyncExternalStore(store.subscribe, store.getState);
const [sameValue, setSameValue] = useState(value);
if (value !== sameValue) setSameValue(value);
return <Text text={value} />;
}
const root = ReactNoop.createRoot();
act(() => {
// Start a render that reads from the store and yields value
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['value:initial']);
await act(() => {
store.set('value:changed');
});
expect(Scheduler).toHaveYielded(['value:changed']);
await act(() => {
store.set('value:initial');
});
expect(Scheduler).toHaveYielded(['value:initial']);
});
}); The last assertion fails with the |
What I don't understand is that in // Check if there was a render phase update
if (didScheduleRenderPhaseUpdateDuringThisPass) { Which runs if That's why The fact that there is code written to manage I must be missing something 😅 how to make sure those effects are run even if component function is called again before end of work? |
Yes, it is legal to call
|
If state is dispatched during render phase, then work in progress `hook` will hold already updated memoized state, defeating previous value comparison. Using `currentHook` instead fixes that issue.
Ok, rel my previous comment, IIUC the discarding of work in progress hook is expected when state is dispatched in render phase. The work in progress queue should be recreated (to reflect new state).
It works when using |
a good solution to splitting UI and business logic, see: https://github.com/hawx1993/hooks-view-model |
…ender phase (#25578) Fix facebook/react#25565
In a custom hook, I forgot to wrap my
setState
call in auseEffect
, 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:
React version: 18
Steps To Reproduce
useSyncExternalStore
(for example redux 8)setState
outside ofuseEffect
or event handlers (in render)Link to code example: https://codesandbox.io/s/react-18-redux-8-reproduce-bug-ojdx43
The current behavior
The expected behavior
Further digging
When I tried to trace down the issue, I found that the
setState
called outsideuseEffect
would mess up with the fiber update queue, in particular the priorpushEffect
supposedly callingupdateStoreInstance
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 unwrappedsetState
.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.The text was updated successfully, but these errors were encountered: