Do you want to request a feature or report a bug?
Bug.
What is the current behavior?
Components using a context update their content upon changes to the context, but render and componentDidUpdate are not invoked even though the the components' content changes.
Here's a JSFiddle example. Note how the component does update (the display on screen changes), but the "render" messages are only logged once (to the console), while the "update" messages are never logged.
What is the expected behavior?
I guess I understand why this is happening - the components which use Context.Consumer don't really re-render or get updated when the context changes; only the Context.Consumer component does. It would still be appreciated to at least make componentDidUpdate get invoked somehow (automatically).
Regardless, this behavior should certainly be documented as it is quite unclear, unintuitive and not so easy to detect.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
Latest React I guess? I'm running on Windows 10.0.17134.799 and Chrome 75.0.3770.142, but I believe it should replicate on other environments as well.
@eyalroth I think this is not a valid issue. The componentDidUpdate lifecycle method is not going to be triggered unless you are updating the current component state or props, in this case, what is going to get updated is whatever you are passing as render prop/children within the Context.Consumer, try something like this:
<ThemeContext.Consumer>
{ ({theme}) => (
<OtherChildComponent theme={theme} />
)}
</ThemeContext.Consumer>
This way you will get updated the OtherChildComponent component (and you can add there the componentDidMount method).
I hope that it helps.
@adielhercules This is not entirely true. componentDidUpdate will also be triggered when the component is forced to update via component.forceUpdate(), and frankly it seems the entire matter is not so clear as already noted in this issue.
Furthermore, in the official documentation on the context and its use-cases it is implied that the component should update:
All consumers that are descendants of a Provider will re-render whenever the Provider鈥檚 value prop changes. The propagation from Provider to its descendant consumers is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component bails out of the update.
I believe this would be true for components using the Class.contextType, though I haven't checked that. If that's true, that would make this whole feature even less intuitive and less consistent. Frankly, it's a bit of a mess to keep the documentation so minimal and without explicit warnings on the pitfalls.
I'm not saying there aren't workarounds, but they are messy and aren't built into the framework, which makes this feature much more cumbersome and prone to error.
The components which use Context.Consumer don't really re-render or get updated when the context changes; only the Context.Consumer component does. It would still be appreciated to at least make componentDidUpdate get invoked somehow (automatically).
@eyalroth you're exactly right, the component rendering the consumer isn't actually rendering again; the Context.Consumer component is rendering and calling the callback that the rendering component provided.
I can see how it's slightly confusing because of the render props API, but it's intentional and desired behavior. Consider a component like Counter that doesn't use context or anything, but does use a render prop API:
function App() {
return (
<div>
Count is: <Counter>{count => <div>{count}</div>}</Counter>
</div>
)
}
When Counter renders it calls the callback passed to it and count gets updated, but App doesn't re-render (demo). I think it's confusing because on the surface it seems like App "owns" the content returned from the render callback, but in reality it's "owned" by Counter. This all holds true for Context.Consumer components as well.
A side note: you can use the useContext hook withh effects to get the behavior you want in a much cleaner way. So maybe consider using Hooks? 馃檪
I believe this would be true for components using the Class.contextType, though I haven't checked that. If that's true, that would make this whole feature even less intuitive and less consistent.
The important part in the documentation you quoted is:
All consumers that are descendants of a Provider will re-render whenever the Provider鈥檚 value prop changes
Consider which component is a _consumer_ in both cases. With Context.Consumer the answer is obvious, it's Context.Consumer! With contextType it's the class component that becomes the consumer. So it's consistent, only the _component acting as a consumer_ gets updated in both cases.
@aweary I find it hard to accept that this behavior is intentional and desired, and for three reasons:
contextType does make the class update while Context.Consumer does not (and that it is not mentioned anywhere in the documentation).props in mind and also had a componentDidUpdate implementation. Later on it is realized that one of the props is in fact shared by many components, and therefore a refactor is due -- after all, it is clearly stated in the documentation that this is one of the reasons for using a context. Now it's up to the programmer to remember this inconvenient detail and work around it. As for the _consumers_ part in the documentation - yes, I took a very careful note at this word, but couldn't decide whether that word refers to all "consuming" components (as mentioned several times prior to this paragraph in the documentation), or literally to the Context.Consumer objects. Again, the inconsistency between the two methods along with the ambiguity regarding the componentDidUpdate method require -- in my opinion -- a clear warning about this in the documentation of the context feature.
To be honest, if I had known earlier about the contextType method I would have used it instead of the Context.Consumer one (I was following the instructions of Gatsby's layout plugin). The only use for the latter as I see it is multiple contexts, which I have no use for.
Most helpful comment
@eyalroth you're exactly right, the component rendering the consumer isn't actually rendering again; the
Context.Consumercomponent is rendering and calling the callback that the rendering component provided.I can see how it's slightly confusing because of the render props API, but it's intentional and desired behavior. Consider a component like
Counterthat doesn't use context or anything, but does use a render prop API:When
Counterrenders it calls the callback passed to it andcountgets updated, butAppdoesn't re-render (demo). I think it's confusing because on the surface it seems likeApp"owns" the content returned from the render callback, but in reality it's "owned" byCounter. This all holds true forContext.Consumercomponents as well.A side note: you can use the
useContexthook withh effects to get the behavior you want in a much cleaner way. So maybe consider using Hooks? 馃檪The important part in the documentation you quoted is:
Consider which component is a _consumer_ in both cases. With
Context.Consumerthe answer is obvious, it'sContext.Consumer! WithcontextTypeit's the class component that becomes the consumer. So it's consistent, only the _component acting as a consumer_ gets updated in both cases.