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)

Most upvoted comments

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)?

Same here, I’m using a dependency injection framework factory method inside useMemo to create one instance of a model inside my component, using react 18 triggers useMemo few times for no reason.

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 useMemo being called multiple times, and sometimes more than just 2, with useEffect being 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 HooksDispatcherOnMount where useMemo callback has a direct execution and the only way to pick the branch is not to have memoizedState in the current fiber.

Meaning:

So the only “stable” way to perform operation once is to perform it in useEffect(hello StrictMode) and save in useState causing re-render, or placing components with useMemo a bit more strategically to have them being rendered a little more stable, ie “above Suspense boundaries”.

This is a real useMemo in React 18.2.0 bug. Here is a simple example where useMemo is triggered twice for each dependency value.

https://codesandbox.io/s/compassionate-swartz-wd3j94?file=/src/App.js

https://iili.io/LEGLkg.png

Or is this normal in strict mode?

Yes, I see this is normal https://reactjs.org/docs/strict-mode.html

I call URL.createObjectURL and need to revokeObjectURL on 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 useMemo is an object. I can fix that, but still this behavior seems to me strange and unexpected.