-
Notifications
You must be signed in to change notification settings - Fork 47.4k
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
Re-throw errors thrown by the renderer at the root in the complete phase #18029
Conversation
React treats errors thrown at the root as a fatal because there's no parent component that can capture it. (This is distinct from an "uncaught error" that isn't wrapped in an error boundary, because in that case we can fall back to deleting the whole tree -- not great, but at least the error is contained to a single root, and React is left in a consistent state.) It turns out we didn't have a test case for this path. The only way it can happen is if the renderer's host config throws. We had similar test cases for host components, but none for the host root. This adds a new test case and fixes a bug where React would keep retrying the root because the `workInProgress` pointer was not advanced to the next fiber. (Which in this case is `null`, since it's the root.) We could consider in the future trying to gracefully exit from certain types of root errors without leaving React in an inconsistent state. For example, we should be able to gracefully exit from errors thrown in the begin phase. For now, I'm treating it like an internal invariant and immediately exiting.
Details of bundled changes.Comparing: 58b8797...348302e react-art
react-native-renderer
react-dom
react-reconciler
react-test-renderer
ReactDOM: size: 0.0%, gzip: -0.0% Size changes (stable) |
Details of bundled changes.Comparing: 58b8797...348302e react-native-renderer
react-test-renderer
react-reconciler
react-dom
react-art
ReactDOM: size: 0.0%, gzip: -0.1% Size changes (experimental) |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
a5a0366
to
2299d1d
Compare
Your description doesn’t describe what this fix does in the code? |
@@ -1285,6 +1285,7 @@ function handleError(root, thrownValue) { | |||
// boundary. | |||
workInProgressRootExitStatus = RootFatalErrored; | |||
workInProgressRootFatalError = thrownValue; | |||
workInProgress = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is happening here? Why does this work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds a new test case and fixes a bug where React would keep retrying the root because the workInProgress pointer was not advanced to the next fiber. (Which in this case is null, since it's the root.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other words, it falls into an infinite loop because workInProgress
is stuck on the the host root fiber forever. Usually workInProgress
gets advanced during either beingWork
or completeUnitOfWork
but since it keeps erroring that never happens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Subtle. A comment saying that this represents moving to the next sibling would be nice but not required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I'll add one! I think there's a potential follow up to also call unwindWork
here to pop the contexts. I originally didn't bother because it's not impossible even that call will throw, but if layout errors are common enough it might be worth doing more to prevent corrupted internal state.
…ase (facebook#18029) * Re-throw errors thrown by the renderer at the root React treats errors thrown at the root as a fatal because there's no parent component that can capture it. (This is distinct from an "uncaught error" that isn't wrapped in an error boundary, because in that case we can fall back to deleting the whole tree -- not great, but at least the error is contained to a single root, and React is left in a consistent state.) It turns out we didn't have a test case for this path. The only way it can happen is if the renderer's host config throws. We had similar test cases for host components, but none for the host root. This adds a new test case and fixes a bug where React would keep retrying the root because the `workInProgress` pointer was not advanced to the next fiber. (Which in this case is `null`, since it's the root.) We could consider in the future trying to gracefully exit from certain types of root errors without leaving React in an inconsistent state. For example, we should be able to gracefully exit from errors thrown in the begin phase. For now, I'm treating it like an internal invariant and immediately exiting. * Add comment
React treats errors thrown at the root as a fatal because there's no parent component that can capture it. (This is distinct from an "uncaught error" that isn't wrapped in an error boundary, because in that case we can fall back to deleting the whole tree – not great, but at least the error is contained to a single root, and React is left in a consistent state.)
It turns out we didn't have a test case for this path. The only way it can happen is if the renderer's host config throws. We had similar test cases for host components, but none for the host root.
This adds a new test case and fixes a bug where React would keep retrying the root because the
workInProgress
pointer was not advanced to the next fiber. (Which in this case is null, since it's the root.)We could consider in the future trying to gracefully exit from certain types of root errors without leaving React in an inconsistent state. For example, we should be able to gracefully exit from errors thrown in the begin phase. For now, I'm treating it like an internal invariant and immediately exiting.