react: Bug: useMemo hook executes twice
I’m using useMemo hook with an empty dependency array in a component withlazy + Suspense, so I expect the function inside useMemo will be called once, but sometimes the function is called twice.
No StrictMode, no rerenders.
useMemo(() => {
console.log('useMemo');
}, []);
React version: 18.2.0
I can’t reproduce it with version 17.0.2
Steps To Reproduce
Please, take a look at the simplified example. I could reproduce it on a regular basis after I’ve added setState call inside useMemo. As the issue is hard to reproduce, there is a script that reloads the page until the bug appears.
Link to code example: https://codesandbox.io/s/smoosh-forest-g6ft5o
Pay attention, that function in useEffect was called once, which is expected behavior, but useMemo was called twice.
In the real project, there is no setState call inside useMemo and no warnings, but anyway I meet the issue every 10-20 page reloads.
If I delete lazy it works as expected. If I drop LongComponent it works as expected.
The current behavior
The function passed to useMemo is executed twice despite the empty dependency array, and the component wasn’t rerendered.
console:
useMemo
useMemo
useEffect
The expected behavior
The function passed to useMemo is executed only once.
console:
useMemo
useEffect
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 10
- Comments: 20 (3 by maintainers)
Why does it matter if it executes once or twice? Since any function inside useMemo should be pure, it should make no difference. Is this a performance concern, or does it change the behavior in some observable way (when you follow the rules)?
Please check this https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily. You can manually save this instance to ref on very first render (or even lazily).
Recently we faced the issue with
useMemobeing called multiple times, and sometimes more than just 2, withuseEffectbeing called once. Wondering how it’s working underneath…Investigation led us to the moment where React picks the right hook implementation - https://github.com/facebook/react/blob/c5b9375767e2c4102d7e5559d383523736f1c902/packages/react-reconciler/src/ReactFiberHooks.js#L543-L559
In our case it was picking
HooksDispatcherOnMountwhereuseMemocallback has a direct execution and the only way to pick the branch is not to havememoizedStatein the current fiber.Meaning:
useReflike per example above would be discarded. HooksDispatcherOnMount has no “previous state”useStatevia callback as per https://legacy.reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily would be discarded. The same “no previous state” case.So the only “stable” way to perform operation once is to perform it in
useEffect(hello StrictMode) and save inuseStatecausing re-render, or placing components with useMemo a bit more strategically to have them being rendered a little more stable, ie “above Suspense boundaries”.Yes, I see this is normal https://reactjs.org/docs/strict-mode.html
I call
URL.createObjectURLand need torevokeObjectURLon unmount. What would be the way to make sure I can revoke every created blob URl?@gaearon Yeah, it causes unpleasant problems with performance, because the returned value from
useMemois an object. I can fix that, but still this behavior seems to me strange and unexpected.