Skip to content
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: strict mode, initial state changes, and useMemo (with dependency) doesn't seem to be recomputed #21178

Closed
aliak00 opened this issue Apr 5, 2021 · 2 comments
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Type: Question

Comments

@aliak00
Copy link

aliak00 commented Apr 5, 2021

React version: 17.0.2

I ran in to this interesting dynamic that I cannot explain today.

  1. The code sets some initial state, which is a random number, and logs this initial value.
  2. It converts the number to a string using useMemo, which logs the number it is converting, and is dependant on that number (not the string). And
  3. when the component is mounted, I force a re-render by changing a boolean state once.

The result is that the initial value of the number is changed (and the log is output) so you see the number being set twice to two different values - this only happens in StrictMode. It is surprising. Is it expected?

You also see the useMemo being computed ONCE - only the first time. So seemingly it does not recompute a new string value.

BUT, the output in the HTML is the correct and shows the stringified value of the second number.

So what is happening here? Is the console log swallowed? Is useMemo behaving correctly, and more importantly, is the initialValue of useState supposed to change like that?

Steps To Reproduce

export default function App() {
  const [rand] = useState(Math.random());
  const [, setState] = useState(false);

  console.log(`Number: ${rand}`);

  useEffect(() => {
    setState(true);
  }, []);

  const text = useMemo(() => {
    console.log(`Computing text from ${rand}`);
    return rand.toString();
  }, [rand]);

  return (
    <div className="App">
      <h1>Rand: {rand}</h1>
      <h1>Text: {text}</h1>
    </div>
  );
}

And wrap your app in StrictMode

Link to code example:

https://codesandbox.io/s/strict-mode-random-bug-ml3gv

The current behavior

initial state computed twice.
usememo seemingly not run twice

The expected behavior

Initial state computed once?
Or usememo runs twice?

@aliak00 aliak00 added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Apr 5, 2021
@bvaughn
Copy link
Contributor

bvaughn commented Apr 5, 2021

Strict mode intentionally runs some code twice in DEV mode only in order to help detect unsafe side effects. So the fact that your function is rendering twice is expected.

To avoid confusion, React temporarily suppresses console logs during the second run (see #18547). This has been somewhat controversial as it can cause confusion. The pros and cons have been discussed in length on other issues though so I'm going to defer to those here and not repeat the arguments. #15315

How did it even become noticeable in this case? Because you're using a random value. React components are expected to be idempotent: Rendering a component multiple times with the same input should always yield the same output. (This includes initial state values.)

The reason it's confusing in this case is that the state is being initialized to a random value rather than a deterministic value. If you have to use something like Math.random(), our recommendation would be to move the generation of the random value to the (e.g. event or whatever that scheduled the update) and pass it to your component as a prop. This way it only runs once and your component can safely be paused or re-rendered later without side effects.

@gaearon
Copy link
Collaborator

gaearon commented Mar 30, 2022

To follow up on this, we've changed the logging behavior in 18 to be more intuitive.
#21783 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Type: Question
Projects
None yet
Development

No branches or pull requests

3 participants