Skip to content

Bug: state is not preverved across re-mounts in StrictMode #25893

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

Closed
barroudjo opened this issue Dec 16, 2022 · 5 comments
Closed

Bug: state is not preverved across re-mounts in StrictMode #25893

barroudjo opened this issue Dec 16, 2022 · 5 comments
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@barroudjo
Copy link

barroudjo commented Dec 16, 2022

State is not preverved across StrictMode re-mounts. If StrictMode is removed there is no bug.

React version: 18.2.0

Steps To Reproduce

  1. https://stackblitz.com/edit/react-strict-mode-cosrnr?file=index.js,index.html
  2. Click on the button, and look at the console. The boolean state in the useSetStateWithoutUseEffect hook is set to true(we have a console.log('setting boolean to true').
    However it is instantly set back to false, even though its setter is not called...
    I have no idea what causes this, but since it only happens in strict mode, and reflects a loss of state (and I can't for the life of me see any bug in the hook useSetStateWithoutUseEffect, as I think setting state conditionnally outside a useEffect or useCallback shouldn't be a problem), I can only assume it's a React bug.

Here is the hook code:

export const useSetStateWithoutUseEffect = (show) => {
  const previousShowRef = useRef();
  const [boolean, setBoolean] = useState(false);
  if (!previousShowRef.current && show) {
    setBoolean(true);
  }
  previousShowRef.current = show;
  return boolean;
};

Link to code example: https://stackblitz.com/edit/react-strict-mode-cosrnr?file=index.js,index.html

The current behavior

The value boolean is set to true as expected on the click, but then instantaneously set back to false, even though its setter is not called...

The expected behavior

The value boolean should be set to true upon clicking and stay that way. (which works in non-strict mode)

EDIT: simplified a LOT the code example

@barroudjo barroudjo added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Dec 16, 2022
@barroudjo
Copy link
Author

I simplified a lot the reproduction scenario, but for archival purposes here is the original: https://stackblitz.com/edit/react-strict-mode-uvjw3r?file=index.js

(newer simpler one is here: https://stackblitz.com/edit/react-strict-mode-cosrnr?file=index.js,index.html)

@sunderls
Copy link
Contributor

This issue is already fixed in #25583

You can try it out by setting dependency in your project to react@next and react-dom@next, I checked it works.
Screen Shot 2022-12-22 at 16 53 40

reason I think why it didn't work in 18.2

It is not about state actually, it is about the useRef you are using.

renderWithHooks() is called twice, with the same current fiber node, meaning it re-renders from scratch.

Screen Shot 2022-12-22 at 16 54 17

In your code, the existence of ref makes your component not idempotent, because

  1. when show becomes true. ref value still false, so setState is called again, and it generates the correct workInProgress, that's why you see the alert and log.
  2. then double rendering kicks in before it is committed to DOM. As mentioned above workInProgress tree is discarded, and renderWithHooks() tries to generate the workInProgress again from scratch. Now the ref has value of true, so the setState() won't happen, which means the state stays false.

@gaearon
Copy link
Collaborator

gaearon commented Apr 14, 2023

We don't consider this a bug.

Your component's rendering function reads ref.current to decide what state to set (and thus what to render). Reading refs during rendering is not supported in React — it's very similar to reading a global variable. It's no longer considered predictable because ref.current is mutable. See "Do not write or read ref.current during rendering" here.

If you want to store information from previous renders, do not use refs for this. Use state. Here's how.

It looks like we "accidentally" fixed this on main but this does not mean that the pattern is supported.

@gaearon gaearon closed this as completed Apr 14, 2023
@barroudjo
Copy link
Author

barroudjo commented Apr 17, 2023

Thanks, that's interesting, it makes me rethink a lot of the ways I used useRef when I wanted to store state without triggering re-renders.
This leads me to wondering if this escape hatch is still valid: https://legacy.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback ?
Because the problem it addresses is very common, and the new doc doesn't seem to feature this escape hatch...

@gaearon
Copy link
Collaborator

gaearon commented Apr 17, 2023

This leads me to wondering if this escape hatch is still valid

This example doesn't read or write refs during rendering, so it's OK.

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
Projects
None yet
Development

No branches or pull requests

3 participants