Skip to content

StrictMode causes crashing (due to issue with <Html> root, and others) #52

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

Open
bryantcodesart opened this issue May 10, 2025 · 3 comments

Comments

@bryantcodesart
Copy link

bryantcodesart commented May 10, 2025

With latest versions of react, three, r3f, a11y packages, I'm encountering an issue in my vite and next projects. In some conditions, it is app crashing.

Here is a reproduction.
https://codesandbox.io/p/sandbox/7rkzpf

You'll see, on load, an error in the console:

You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.

If you unmount and remount the <A11y> component, with the provided button, the app will crash:

Cannot update an unmounted root.

(This is a simplification of the way my apps are currently crashing.)

THIS IS NOT AN ISSUE OUTSIDE OF STRICT MODE. As far as I can tell, removing the strict wrapper from my repro removes the issue. But that is not an option for me in my projects.

I'm no wizard! But, to my naive eye, all of this seems to be related to increased strictness with root handling in react. It seems the <Html> component might need to:

  • Maintain reference to the root and not redeclare it if its been declared.
  • Not call render on a root that is unmounted. (It seems the rendering effect can occasionally be called after the cleanup effect that unmounts the root?)

I forked and tried to fix. I was able to stop the crashing by naively implementing the above. E.g. creating el and root together in the same state hook and using an isUnmounted ref as a check before render calls. However, I don't understand this library and all of its many a11y concerns to be confident in my fix--and I think I broke behaviors. (Tab focus stopped working properly after remount.)

When I replaced the component with the drei <Html> component in my fork, all seemed to work just fine.

But again--I might have introduced issues too subtle for me to notice!

@bryantcodesart bryantcodesart changed the title Crashing Issue with <Html> root in dev/strict mode Crashing Issue with <Html> root in dev/strict mode with latest react/three/r3f/a11y packages May 10, 2025
@bryantcodesart
Copy link
Author

Ah! I feel silly for not noticing but there's a comment in the embedded <Html> component in this library indicating it was originally a fork of drei's!

So maybe it's just a matter of catching up!

@bryantcodesart
Copy link
Author

bryantcodesart commented May 14, 2025

Confirmed that this issue happens with older versions of all of these packages (including react 18) when you wrap Canvas children with <StrictMode>.

I suppose the inheritance of StrictMode across the Canvas boundary is now automatic in the latest versions https://r3f.docs.pmnd.rs/tutorials/v9-migration-guide#strictmode which is what surfaced the issue--but the issue is apparently not SPECIFIC to these new versions. It is just an issue with StrictMode in general.

@bryantcodesart bryantcodesart changed the title Crashing Issue with <Html> root in dev/strict mode with latest react/three/r3f/a11y packages StrictMode causes crashing (due to issue with <Html> root) May 14, 2025
@bryantcodesart bryantcodesart changed the title StrictMode causes crashing (due to issue with <Html> root) StrictMode causes crashing (due to issue with <Html> root, and others) May 14, 2025
@bryantcodesart
Copy link
Author

bryantcodesart commented May 14, 2025

Seems like this effect is also a problem in StrictMode.

useEffect(() => {

The double-firing of the effect sets the componentIsMounted ref to false. This aborts the call to setA11yState in handleOnPointerOut. The result is that the state never updates to reflect an element is unfocused/unhovered.

Not sure if you want to separate all the StrictMode issues or batch them into one thread. But I'll just keep documenting what I find.

Possible fix:

  const componentIsMounted = useRef(true);
  useEffect(() => {
    componentIsMounted.current = true;
    return () => {
      domElement.style.cursor = "default";
      componentIsMounted.current = false;
    };
  }, []); 

Adding componentIsMounted.current = true; to the effect seemed to clear it up for me in my projects and I THINK is correct. This way the value gets reset when the component remounts. But that might have unforeseen consequences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant