-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
How to implement shouldComponentUpdate with this.context? #2517
Comments
This is an extremely good question. Thanks for raising it! This is a case where you're manually pruning/optimizing the tree recalculation. It seems to me that you'd need to request access to any context variables that are of relevance to your calculation. We could also consider doing other fancy things like pass in a hash of the full context (or even the full context) if necessary. @sebmarkbage - thoughts? |
Yeah, the problem is middle components don't know what context some distant child might need. There is no way they can know which |
I wonder if context propagation could happen in a separate tree traversal, without being blocked by falsy Basically, when parent's context changes, it should mark all its descendants that receive this context as dirty, no matter how distant. Ancestor's context change should have the same effect as a state change for descendants who opt into this context—they should receive new |
@gaearon I think in that case you need to re-render everything and so |
I think that http://jsbin.com/geseduneso/2/edit?js,output (If this was the case, both numbers would be incrementing) Where is the inconsistency? |
To put it differently, I think I don't mean the actual implementation—but I want to show that this model is not any more inconsistent than any Flux app with |
Edit: this won't work because parent may change I talked to Glenjamin on IRC and he convinced me changing context might be a bad idea per se. You lose ability to reason about why something was updated if some root update implicitly causes different child updates. But then, the only reasonable solution I see is to completely forbid context changes. That is, make This would make context a hell lot simpler to think about. We can remove third parameter from In case when you need updates from context owner (e.g. like react-router wants Is this a reasonable solution? |
I think the key question to answer is:
I've been using context for passing object references around quite happily, as I only ever write to those objects, not read. eg. |
The use case for context has been for parameters that are applicable across a potentially large subtree, but which you don't want to pass through more generic container nodes. An example might be a color theme, where a large number of nodes might listen to see if the background color is supposed to be white or black; you wouldn't want to pass those around everywhere as parameters. Making getChildContext() behave more like getInitialState() wouldn't actually solve the problem, because you could always replace a parent node which provides a given context value with another parent node that provides a different context value. For the affected subtree, this is indistinguishable from mutating the context variable's value. I think we can find a solution that avoids having users wire up change listeners. I think @andreypopp may be right. Or at the very least, we need to provide a way for shouldComponentUpdate to know if anything in the context has changed, so it can decide to always return true if there was a context variable change. I'll chat with @sebmarkbage later today and see what he thinks. |
Ouch. You're totally right, I haven't considered this. |
@JSFB On a second thought, I don't quite understand your comment. If you replace a parent node, entire child subtree will re-mount anyway, wouldn't it? |
Currently, yes, it will re-mount, but that is partly an implementation detail. You can imagine (and we have been discussing the implementations and ramifications of) reparenting subtrees without loosing node state. @sebmarkbage and I chatted, and came to the following conclusions:
@sebmarkbage, let me know if I missed anything! Good discussions though! Thanks for all the feedback! |
Thank you for taking time to discuss this!
If a top-level This gets even worse when frameworks (such as most popular React routing framework) use context and you may not even be aware of it. Somewhere, there are apps that don't change active link state because somebody optimized top-level components and had literally no idea they had to declare corresponding There is no way components are aware of all their possible descendants. Basically, if I move a context-dependant component anywhere inside a Some frameworks on top of React, such as @swannodette's Om, make I agree this is not blocking for your work, but I can't agree that current situation is satisfactory if context is to be used at all. It's not just hard to implement Popular libraries already rely on context heavily. I understand it's not a supported feature, but in my opinion it either needs to at least be possible to make it work with |
This has been known from the start with introduced contexts. We need to be able to have undocumented and unsupported features as experimental features to be able to iterate on them to find special cases. For example, if we didn't have contexts we wouldn't have known that they need to changed to be container-based instead of owner-based. Subtle breakage is part of the contract of using undocumented features. I think what we need to do is by-pass |
I suppose this could work because context is hardly meant to change often. |
Correct, it was always kind of useless. |
This makes a lot of sense. Context changes in the parent need to override falsy But it begs the question: how do we know when context changes? For state and props we have |
I'm using contexts as a way of managing store references. I like this because I can explicitly define what stores are required, so
So, looking at an example, With my implementation, store data is accessed only through getters. My elements do not cache store data. I want a single version of the truth. As a simple non-async example, my render() would typically have something like var peopleStore = this.context.peopleStore;
var person = peopleStore.get(this.props.personId);
return <div>person.fullName</div>; Components attach listeners to stores. I haven't fully decided on the granularity. Stores all have an onChange event. However, I haven't decided on two other things,
In this example, if "people" was "fb users", it's a large complex async store. Should I reuse the store structure for a PersonStore? One for general collection (getFriends, getPerson, etc) but many unique instances of the Person store type for an individual person. So in my example, my Component requires the People store as a context param. Then it uses the personId prop to identify and subscribe to a specific Person store. Now, to introduce some dynamic changes. Let's say the currently logged in user logs out and someone else logs in. Ignoring the fact that you'd probably redirect/refresh the page in a real application, how would this element update? Let's also assume that this element is still on the page and isn't just destroyed. I would expect the application logic to first remove/destroy the existing People store. For that, my component needs to stop listening for updates. For this, I would recommend a ReactClass API. Eg, onContextChange(prev, new) The element can then compare the two peopleStore instances. Let's ignore public data and assume the new PeopleStore is null. The element would unsubscribe from the previous Store and trigger a render(). The render would then show some 'user unknown' type message. When the user logs in as another user, a new Store is created, the element reattaches, and render() does its thing with new data. Behind the scenes, "this.render()" couldn't be synchronous. For any of my design/example to make any sense at all, the render() calls need to be collected by the framework and batched together. Unlike props, listening to stores is outside of the core React role of managing the rendering. That's why I don't think a context change should be involved in shouldComponentUpdate(). And despite my example including changing context values (a new store object), I don't think stores are going to be immutable in their high-level nature. I think that a typical flux application design will operate with a subscriber model, callbacks for async, etc. The base store object will usually live for the life of the application. |
There's quite a bit of interest in this. (See references above.) |
Yes @gaearon . I want to pass localization data in context, and let the user be able to change its language at runtime, letting all the components re-render themselves with the updated translations,currencies, date formattings... As far as I understand, this seems hard to do for now. See also |
@gpbl check that: It seems to me that in case we want to re-render the whole app from top in case of context change, we can unmount and remount the node in the same event loop tick, and it does not produce flickering effects (constantly). This is a perfect workaround for dealing with user language changes. |
@slorber Calling I think it is time to declare context an official feature. Not using context is not an option for most people, because the router and ReactIntl uses it. |
Is there any solution right now? |
As we know from this thread the existing solution is pretty fundamentally broken. Therefore we propose a new API that handles the same use cases but doesn’t have those design flaws. The plan is for APIs to exist side by side, and later to phase out the old API when people have migrated. Check out the discussion: reactjs/rfcs#2 |
@acdlite just landed a PR with the new context API: #11818. I think we can consider this closed. The new API doesn't have this problem. The old API can't be fixed, and we'll deprecate it at some point after major libraries upgrade to the new API. The new API will be available and documented in one of the next minor React 16.x releases. |
@gaearon Not sure if you hear this frequently enough, but: you guys are doing a great job and I appreciate your work. 🍺 |
I know
this.context
is not officially there but quite a few libraries rely on it, and it seems like it's getting into shape with #2509.I'm trying to understand how exactly
shouldComponentUpdate
is supposed to be implemented withcontext
in mind. I noticed it accepts a third argument (nextContext
) and I can extendPureRenderMixin
to also check it:Components that don't opt into
this.context
by not omittingcontextTypes
will not get this third argument, which is understandable.However this presents a problem when we have a
<Middle />
component in between between<Top />
context owner and<Bottom />
context consumer. If<Middle />
implements a restrictiveshouldComponentUpdate
, there is no way for<Bottom />
to react to<Top />
's context updates at all:(fiddle)
The same problem would occur if I tried to give
Middle
a generic context-awareshouldComponentUpdate
as I wrote above, becauseMiddle
has nothis.context
unless it opts in.This is possible to work around by adding
contextTypes
toMiddle
, but it doesn't look like a good solution. You'd need to explicitly add necessarycontextTypes
on every level with smartshouldComponentUpdate
so it's too easy to slip up.Will this be solved by #2112? Is there another solution in the meantime? What is the recommended way?
The text was updated successfully, but these errors were encountered: