-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Fixed an issue with nested contexts unwinding when server rendering. Issue #12984 #12985
Conversation
// find the correct previous provider based on type | ||
let previousProvider; | ||
if (this.providerIndex > -1) { | ||
for (let i = 0; i <= this.providerIndex; i += 1) { |
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.
Instead of searching from the bottom, shouldn't we start search at the top and stop at the first match? Try adding more nesting to the test.
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.
Duh. Fixed.
let previousProvider; | ||
if (this.providerIndex > -1) { | ||
for (let i = 0; i <= this.providerIndex; i += 1) { | ||
if (this.providerStack[i] && |
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.
Please be explicit in type checks (e.g. !== null
if that's what you mean). Don't rely on falsiness.
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 wasn't sure if there was a more Flow appropriate way to check. Flow wanted explicit checks for both null
and undefined
.
@@ -692,8 +692,8 @@ class ReactDOMServerRenderer { | |||
// find the correct previous provider based on type | |||
let previousProvider; | |||
if (this.providerIndex > -1) { | |||
for (let i = 0; i <= this.providerIndex; i += 1) { | |||
if (this.providerStack[i] && | |||
for (let i = this.providerIndex; i >= 0; i -= 1) { |
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.
If you don't mind, i--
would feel a bit more intuitive to me
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.
Done. I'm used to not allowing infix from our projects lint rules, old habits.
for (let i = 0; i <= this.providerIndex; i += 1) { | ||
if (this.providerStack[i] && | ||
for (let i = this.providerIndex; i >= 0; i -= 1) { | ||
if (this.providerStack[i] !== null && this.providerStack[i] !== undefined && |
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.
So, do we actually need those checks? I wouldn't expect a provider "below" to ever be null
. We only clear them out as we pop. As long as we check i
doesn't get below zero we should be fine.
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.
Agreed. But, Flow doesn't seem to be aware of that. Perhaps we need some special Flow syntax I'm not aware of, which is highly likely?
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 addressed this by changing the array definition to be stricter and pop via setting the length. It didn't seem right to allow null
as an array item but then not test for null
when iterating over the array, even though the logic of how the array was being manipulated was OK.
@@ -692,8 +692,8 @@ class ReactDOMServerRenderer { | |||
// find the correct previous provider based on type | |||
let previousProvider; | |||
if (this.providerIndex > -1) { |
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.
Seems like we can remove this condition? For loop already covers it.
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.
} | ||
} | ||
} | ||
if (previousProvider) { |
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.
Let's initialize it to null
and then compare to null
instead of a truthy check.
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.
Good idea.
if (this.providerIndex > -1) { | ||
for (let i = this.providerIndex; i >= 0; i--) { | ||
if (this.providerStack[i] !== null && this.providerStack[i] !== undefined && | ||
(this.providerStack[i]: ReactProvider<any>).type === provider.type) { |
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 comment was about why we felt comfortable using Flow any
:
// We assume this type is correct because of the index check above.
Please keep it above the any
case. Maybe change to "Flow type" to disambiguate.
// We assume this Flow type is correct because of the index check above.
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.
GH collapsed some older comments but they're still relevant. Thanks! |
AFAICT The client implementation uses a stack of cursors for popping/pushing context providers without iteration. Could that same approach be reused here? |
We have many kinds of contexts there (even for a single provider) but here we have just one so I thought normally you probably wouldn't need to go too high up. So we could consider this but I'd prefer to separate it from the bugfix. |
@@ -686,17 +686,22 @@ class ReactDOMServerRenderer { | |||
'Unexpected pop.', | |||
); | |||
} | |||
this.providerStack[this.providerIndex] = 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 does .length
assignment do in V8? I'd like to make sure it doesn't think the array length change is likely to "stay" because it changes all the time. I think just using null
is more straightforward and less surprising to the engine (even though the typing isn't as nice for us).
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. But, going back to using null
seems to require testing for null
when accessing array items by index, since Flow has been told that null
and undefined
are expected array item values. Or, is there an alternate Flow syntax that allows us to remove the explicit checks?
let previousProvider = null; | ||
for (let i = this.providerIndex; i >= 0; i--) { | ||
// We assume this Flow type is correct because of the index check above. | ||
if ((this.providerStack[i]: ReactProvider<any>).type === provider.type) { |
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.
Please read this.providerStack[i]
once. (Move it to a constant.)
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.
Yep.
const previousProvider: ReactProvider<any> = (this.providerStack[ | ||
this.providerIndex | ||
]: any); | ||
// find the correct previous provider based on type |
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 comment is redundant, IMO the code speaks for itself
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
What do you mean by just having one here? You can use any number of context instances right? For example: const Foo = React.createContext("foo");
const Bar = React.createContext("bar");
const App = () => (
<Foo.Provider value="new foo">
<Bar.Provider>
<Bar.Provider>
{/* Imagine this is nested within a bunch of Bar.Providers */}
<Foo.Provider>{children}</Foo.Provider>
</Bar.Provider>
</Bar.Provider>
</Foo.Provider>
); When the deeply nested |
I mean one per provider (in Fiber every provider has three context stack values associated with it). And we also keep track of host context and a bunch of other things in that client stack. So there is good justification for over-engineering that client stack. I agree with what you're saying in general. If it's avoidable let's avoid it. |
Details of bundled changes.Comparing: c5a733e...5114a3e react-dom
Generated by 🚫 dangerJS |
priorProvider.type === provider.type) { | ||
if ( | ||
priorProvider !== null && | ||
priorProvider !== undefined && |
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.
These checks aren’t truly necessary, are they? We know they can’t be empty because we’re moving to the left and check the index.
If type system disagrees I think we should do an unsafe cast with a comment.
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.
Happy to. I wasn't sure how strict you wanted to be with the type checking.
@ericsoderberghp @gaearon Is there any changes left to make on this PR ? Is there any way I can help ? It's impacting us at GitBook, I've never contributed to the react core, but I'd be happy to start :) |
Hi! One thing that concerns me about this solution is that it's O(n) where n is the number of providers on the stack. It would be good to avoid iterating over them as we pop them. I'll get this fix in but I'll file a followup issue to try to remove the iteration. Do you want to try to help with this? I'm a bit hesitant to cut a release without a fix for this. |
omg did I just accidentally write some Python |
I think what it means is that at the time A2 is encountered, you need a reference to A1 to be able to push it. |
Yep, so we keep two stacks. One for "previous values to restore to" and one for "providers who need restoration". |
OK. I'm happy to work on this if you'd like the help. |
I have it running so I'll send a PR shortly. Would appreciate a review. |
193: Update dependency react to v16.4.1 r=magopian a=renovate[bot] This Pull Request updates dependency [react](https://github.com/facebook/react) from `v16.4.0` to `v16.4.1` <details> <summary>Release Notes</summary> ### [`v16.4.1`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1641-June-13-2018) [Compare Source](facebook/react@v16.4.0...v16.4.1) ##### React * You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@​bvaughn] in [#​12911](`https://github.com/facebook/react/pull/12911`)) ##### React DOM * Fix a crash when the input `type` changes from some other types to `text`. ([@​spirosikmd] in [#​12135](`https://github.com/facebook/react/pull/12135`)) * Fix a crash in IE11 when restoring focus to an SVG element. ([@​ThaddeusJiang] in [#​12996](`https://github.com/facebook/react/pull/12996`)) * Fix a range input not updating in some cases. ([@​Illu] in [#​12939](`https://github.com/facebook/react/pull/12939`)) * Fix input validation triggering unnecessarily in Firefox. ([@​nhunzaker] in [#​12925](`https://github.com/facebook/react/pull/12925`)) * Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@​nhunzaker] in [#​12976](`https://github.com/facebook/react/pull/12976`)) * Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@​philipp-spiess] in [#​12966](`https://github.com/facebook/react/pull/12966`)) ##### React DOM Server * Fix an incorrect value being provided by new context API. ([@​ericsoderberghp] in [#​12985](`https://github.com/facebook/react/pull/12985`), [@​gaearon] in [#​13019](`https://github.com/facebook/react/pull/13019`)) ##### React Test Renderer * Allow multiple root children in test renderer traversal API. ([@​gaearon] in [#​13017](`https://github.com/facebook/react/pull/13017`)) * Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@​fatfisz] in [#​13030](`https://github.com/facebook/react/pull/13030`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
25: Update react monorepo to v16.4.1 r=renovate[bot] a=renovate[bot] This Pull Request renovates the package group "react monorepo". - [react-dom](https://github.com/facebook/react) (`dependencies`): from `16.4.0` to `16.4.1` - [react](https://github.com/facebook/react) (`dependencies`): from `16.4.0` to `16.4.1` # Release Notes <details> <summary>facebook/react</summary> ### [`v16.4.1`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1641-June-13-2018) [Compare Source](facebook/react@v16.4.0...v16.4.1) ##### React * You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@​bvaughn] in [#​12911](`https://github.com/facebook/react/pull/12911`)) ##### React DOM * Fix a crash when the input `type` changes from some other types to `text`. ([@​spirosikmd] in [#​12135](`https://github.com/facebook/react/pull/12135`)) * Fix a crash in IE11 when restoring focus to an SVG element. ([@​ThaddeusJiang] in [#​12996](`https://github.com/facebook/react/pull/12996`)) * Fix a range input not updating in some cases. ([@​Illu] in [#​12939](`https://github.com/facebook/react/pull/12939`)) * Fix input validation triggering unnecessarily in Firefox. ([@​nhunzaker] in [#​12925](`https://github.com/facebook/react/pull/12925`)) * Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@​nhunzaker] in [#​12976](`https://github.com/facebook/react/pull/12976`)) * Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@​philipp-spiess] in [#​12966](`https://github.com/facebook/react/pull/12966`)) ##### React DOM Server * Fix an incorrect value being provided by new context API. ([@​ericsoderberghp] in [#​12985](`https://github.com/facebook/react/pull/12985`), [@​gaearon] in [#​13019](`https://github.com/facebook/react/pull/13019`)) ##### React Test Renderer * Allow multiple root children in test renderer traversal API. ([@​gaearon] in [#​13017](`https://github.com/facebook/react/pull/13017`)) * Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@​fatfisz] in [#​13030](`https://github.com/facebook/react/pull/13030`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
The key change her is to unwind to the previous provider of the same type as the provider being popped.
Addresses #12984.