-
Notifications
You must be signed in to change notification settings - Fork 47.5k
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
Fix context handling for errors thrown in ClassComponent render #8604
Fix context handling for errors thrown in ClassComponent render #8604
Conversation
@@ -1975,4 +1975,46 @@ describe('ReactIncremental', () => { | |||
instance.setState({}); | |||
ReactNoop.flush(); | |||
}); | |||
|
|||
fit('maintains the correct context index when unwinding due to an error ocurring during render', () => { |
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.
fit
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.
Gah!
5a914bd
to
4227318
Compare
efad51d
to
6c36cc1
Compare
Context providers are currently popped one too many times if an error is thrown by a context-provider's render method. In this case, the begin phase is exited before context is pushed but we still pop it in. This PR adds a test for this behavior (failing before) and fixes it by ensuring that we always push context before continuing (or unwinding).
6c36cc1
to
b86cdde
Compare
ReactCurrentOwner.current = workInProgress; | ||
const nextChildren = instance.render(); | ||
reconcileChildren(current, workInProgress, nextChildren); | ||
} finally { |
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.
try blocks prevent the containing function from being optimized. Can you find a way to solve this problem without adding a new try block? Maybe by fixing it in unwindContext
?
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.
I could move the call to instance.render
into a separate function that is try/finally wrapped to minimize the impact. (I'm not an expert with the whole inlining area.) In its currently form I think it would be difficult to fix things in unwindContext
.
Although I'm working today on an effort to combine ReactFiberContext
and ReactFiberHostContext
and add fiber-level tracking so that we won't pop a Fiber more than once, or pop one that wasn't pushed. It will hopefully ease the pressure here a bit as it would enable us to just dev-warn and ignore the bad pop rather than throw an error.
I think this now already works on master. |
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.
Just leave the test.
I could, although I don't think Sebastian's recent change fixed this specific issue. It looks to me like we're still not properly handling an error thrown in I just ran this test against my branch #8611 (which was rebased against master as of a few moments ago) and it still fails due to an "Unexpected Fiber popped" warning. Thoughts? |
Two alternative solutions: A) If something fails to call the constructor, then nothing will be set of stateNode which will cause isContextProvider to be false. All you have to do is make sure that we always push context right after stateNode is set, because that's the indicator whether this is a context provider or not when unwinding the context. This will always be way before B) We currently identify something as a context provider by what's on the instance. However, all context providers are also required to have a static |
Oops, I thought this was passing. I must have copy pasted the wrong test, sorry.
I have a wip branch that tries to do this but it gets easy to miss because we have to do it from
I prefer this solution. We'd also need to mutate current of |
Mutating |
If you fix this could you please also check #8611 (comment) does not reproduce? |
You can still pass fiber to |
Okay. I guess that's fair.
I'm not sure I have quite enough context on that comment. If I add the |
You need to temporarily flip diff --git a/src/renderers/dom/shared/ReactDOMFeatureFlags.js b/src/renderers/dom/shared/ReactDOMFeatureFlags.js
index 5e9d932..17bae0a 100644
--- a/src/renderers/dom/shared/ReactDOMFeatureFlags.js
+++ b/src/renderers/dom/shared/ReactDOMFeatureFlags.js
@@ -13,7 +13,7 @@
var ReactDOMFeatureFlags = {
useCreateElement: true,
- useFiber: false,
+ useFiber: true,
};
module.exports = ReactDOMFeatureFlags;
diff --git a/src/renderers/shared/shared/__tests__/ReactErrorBoundaries-test.js b/src/renderers/shared/shared/__tests__/ReactErrorBoundaries-test.js
index 6ec4e36..23a5744 100644
--- a/src/renderers/shared/shared/__tests__/ReactErrorBoundaries-test.js
+++ b/src/renderers/shared/shared/__tests__/ReactErrorBoundaries-test.js
@@ -73,6 +73,12 @@ describe('ReactErrorBoundaries', () => {
};
BrokenComponentWillMount = class extends React.Component {
+ static childContextTypes = {
+ foo: React.PropTypes.number
+ };
+ getChildContext() {
+ return {foo: 42};
+ }
constructor(props) {
super(props);
log.push('BrokenComponentWillMount constructor'); |
Yeah, I know to flip that flag. Maybe I forgot to flip it though when bouncing back and forth between branches today. 😄 My bad. I'll make sure to verify both fixes! |
Also rename another test to have a shorter name.
I'm not sure if you started working on this but I sent a PR to fix this: #8627. |
Closing this PR in favor of #8627 |
* Push class context providers early Previously we used to push them only after the instance was available. This caused issues in cases an error is thrown during componentWillMount(). In that case we never got to pushing the provider in the begin phase, but in complete phase the provider check returned true since the instance existed by that point. As a result we got mismatching context pops. We solve the issue by making the context check independent of whether the instance actually exists. Instead we're checking the type itself. This lets us push class context early. However there's another problem: we might not know the context value. If the instance is not yet created, we can't call getChildContext on it. To fix this, we are introducing a way to replace current value on the stack, and a way to read the previous value. This also helps remove some branching and split the memoized from invalidated code paths. * Add a now-passing test from #8604 Also rename another test to have a shorter name. * Move isContextProvider() checks into push() and pop() All uses of push() and pop() are guarded by it anyway. This makes it more similar to how we use host context. There is only one other place where isContextProvider() is used and that's legacy code needed for renderSubtree(). * Clarify why we read the previous context * Use invariant instead of throwing an error * Fix off-by-one in ReactFiberStack * Manually keep track of the last parent context The previous algorithm was flawed and worked by accident, as shown by the failing tests after an off-by-one was fixed. The implementation of getPrevious() was incorrect because the context stack currently has no notion of a previous value per cursor. Instead, we are caching the previous value directly in the ReactFiberContext in a local variable. Additionally, we are using push() and pop() instead of adding a new replace() method.
Context providers are currently popped one too many times if an error is thrown by a context-provider's
render
method. In this case the begin phase is exited before context is pushed but we still pop it in.This PR adds a test for this behavior (failing before) and fixes it by ensuring that we always push context before continuing (or unwinding). FWIW we plan to further clean-up and improve the handling of context-related code soon so this specific fix might be short-lived.
This PR relates to #8593.
Remaining work