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: Elements in ErrorBoundary are created twice, one of them becomes an orphan #26518

Closed
KurtGokhan opened this issue Mar 30, 2023 · 6 comments

Comments

@KurtGokhan
Copy link

React version: 18.2.0

Reproducible with 18.3.0-next-85de6fde5-20230328 as well.

As I observed, this is not an issue only in React-DOM. It happens with other renderers too. It can be an issue in the Reconciler or the main React package.

Issue does not seem to happen with React 17 based rendering (legacy mode?). You can test this by uncommenting the code at the end of the code example. However this can be simply because React 17 doesn't throw the error twice. In concurrent mode, error is thrown twice, so two ErrorBoundary are created. The stack traces for two errors look almost the same, except the latter error contains recoverFromConcurrentError in its call stack.

Steps To Reproduce

  1. Create an ErrorBoundary class which renders some basic HTML
  2. Notice how the HTML elements are created twice.

Link to code example: Visit the CodeSandbox example and observe the logs.

In the code example, I mocked the document.createElement to see that it is called twice. However, one of the created elements aren't added to the DOM (becomes orphan).

This may not be a big deal in HTML, but it can be a problem in other renderers where creating elements may have side effects. Also if the element has children, those children will also be created, potentially causing performance issues.

The current behavior

Native elements in ErrorBoundary are created twice, even though one of them are not added to the rendered tree.

The expected behavior

Elements should be created once.

@KurtGokhan KurtGokhan added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Mar 30, 2023
Copy link

github-actions bot commented Apr 9, 2024

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Apr 9, 2024
@KurtGokhan
Copy link
Author

The issue is still reproducible in v19 canary. Stackblitz example here.

@eps1lon eps1lon added Component: Reconciler and removed Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Resolution: Stale Automatically closed due to inactivity labels Apr 10, 2024
@eps1lon
Copy link
Collaborator

eps1lon commented Apr 28, 2024

When React is doing a concurrent render and something throws, it will re-render one more time synchronously in case the error was caused by inconsistent data stores. There is currently a bug where we're also doing that during a synchronous render which is wasteful.

However, creating elements twice would be intended if the error happened during a concurrent render since we'd render twice. Host instance creation (e.g. document.createElement) happens during render phase. Since we only attach the final element to the DOM, previous elements should be garbage collected. There shouldn't be any problem with calling document.createElement multiple times apart from performance. But sometimes we do need to create the element ahead of time.

but it can be a problem in other renderers where creating elements may have side effects

In which renderers would that be a problem? The reconciler docs call it out explicitly that host instance creation happens during render not commit and must be side-effect free: https://github.com/facebook/react/blob/main/packages/react-reconciler/README.md#createinstancetype-props-rootcontainer-hostcontext-internalhandle

@KurtGokhan
Copy link
Author

KurtGokhan commented Apr 28, 2024

In which renderers would that be a problem?

I noticed this in my custom renderer for Unity. Due to the nature of the platform, host instance creation had side-effects. I fixed it in my renderer after noticing this issue.

But I can imagine this could happen in some Web Components if the author isn't aware of this maybe?

The reconciler docs call it out explicitly that host instance creation happens during render not commit and must be side-effect free

Thanks for this. I didn't know that. That particular line may not have existed in the docs back when I read it. Also this is the only case I am aware of, where a host instance is created and discarded without ever being mounted. So I was a bit surprised.

Creating elements twice would be intended if the error happened during a concurrent render since we'd render twice

Just to clarify: The error doesn't happen inside the ErrorBoundary fallback. Is it still ok that the fallback is rendered twice, not only the component which throws the error? Or maybe I should say it like this. Maybe ErrorBoundary shouldn't render in error state until synchronous render is also tried and fails.

@eps1lon
Copy link
Collaborator

eps1lon commented Apr 29, 2024

Thanks for this. I didn't know that. That particular line may not have existed in the docs back when I read it. Also this is the only case I am aware of, where a host instance is created and discarded without ever being mounted.

There are various cases where this could happen since React can render multiple times before actually committing. A concrete case would be inside a Suspense boundary. A component may finish rendering but then a sibling suspended. When the sibling resolves, the original render may have to be discarded.

Just to clarify: The error doesn't happen inside the ErrorBoundary fallback. Is it still ok that the fallback is rendered twice, not only the component which throws the error?

We could investigate that but that wouldn't resolve the original issue, namely that host instance creation in your specific host has side-effects.

@KurtGokhan
Copy link
Author

As long as it is intended, it is not an issue for me. It is just a tiny bit wasteful in terms of performance, but not a big priority.

@rickhanlonii rickhanlonii closed this as not planned Won't fix, can't repro, duplicate, stale May 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants