-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Dont recreate maked context unless unmasked context changes #8706
Dont recreate maked context unless unmasked context changes #8706
Conversation
Seems fine to me. We can just remove masking later if we want to. |
In general, we shouldn't be using the instance to store memoized values because it's shared between both copies of the fiber, as we discussed. But since context is already broken for this purpose, maybe we don't need to care for now. |
Agreed. That's why I mentioned the prior art. Seems like we need to clean up (or rethink) context at some point. This is just another band-aid. |
Doing so can lead to infinite loops if componentWillReceiveProps() calls setState()
af5c342
to
a682059
Compare
This is very different from the provider since this is for every recipient. We expect there to be many more recipients and that those will update much more frequently than the providers. Also this is adding more to the already broken solution that compares props equality. We should fix that with a tag rather than piling on. |
I don't understand the first half of this comment. I'd love to talk about this with you when you're back in the office. Maybe we could just do that? Would be easier. |
@bvaughn |
What I meant was that I don't understand the implications of what @sebmarkbage is saying. This PR added caching for unmasked and masked Edit: (Aside from the fact that using the instance to cache values is hack and error-prone) is the concern that I'm caching this value on the wrong instance? As in I'm caching a value on an instance that doesn't actually originate with that instance. |
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.
Providers don't have masked contexts unless they're also receivers.
// Cache unmasked context so we can avoid recreating masked context unless necessary. | ||
// ReactFiberContext usually updates this cache but can't for newly-created instances. | ||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext; | ||
instance.__reactInternalMemoizedMaskedChildContext = context; |
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.
Are we doing this on every instance? That's probably not great.
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.
Oops, I missed this. 😞
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.
For the context, before this PR we used to only set the hidden field on the context consumers, not on all instances. The rationale is that only components that actually use the context should pay the price for it, not intermediate components.
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.
Would it help to only track this on context
consumers?
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.
Yes. (But ideally we shouldn't check if something is a consumer twice either.)
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.
Ideally we also shouldn't use __reactInternal[...]
fields outside of FiberContext
file. Not that it matters a lot but it’s hacky and I want to keep it contained.
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 agree. I mentioned that as a hack in the PR description.
I could move the setting of that hidden field into a helper function inside of ReactFiberContext
to at least keep the naming of the field there. Briefly considered doing that initially.
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.
Is #8723 an improvement?
Yeah, I'm kind of cheating by caching the masked and unmasked |
The problem
Fiber memoizes the merged
context
object for context-providers but does not memoize the maskedcontext
object. This causedcomponentWillReceiveProps
to be called tpo frequently forcontext
consumers which can result in infinite loops (eg ifcomponentWillReceiveProps()
callssetState()
).A minimal reproduction of one such infinite loop has been added as a test. It looks a little silly but it is greatly reduced from a bug I was investigating in a larger, real-world app.
The proposed solution
Given the way
context
is currently implemented, I think the only solution to this issue is to store memoized copies of both the unmasked and maskedcontext
objects in order to avoid unnecessarily recreating the maskedcontext
. (Stack already does something like this.)Concerns
The solution in this PR is kind of awkward due to the facts that:
context
on a class instance rather than the Fiber to avoid adding 2 additional fields to all Fibers (because most won't consumecontext
). Prior art is__reactInternalMemoizedMaskedChildContext
.context
is created so we have to initially set the cached object outside ofReactFiberContext
.This change is kind of flimsy, but so is
context
in general at the moment (see this comment for elaboration).Let's talk about it. 😄