It's a great introduction to Context, and he very neatly lays out the primary use cases for it. For example, when you simply want to share data between nested components without constantly passing props down.
Yet he brings up an issue that I have seen in other places as well- namely that it does not provide the convenience that these aforementioned libraries do.
There seems to be an idea out there that Context is mostly suited to "read-only" situations. In my opinion, this is not the case at all.
In my previous post, I provided an example of how you could create a simple action where the Context Provider lives:
Another argument is that asynchronous actions (API calls) are particularly problematic in this new paradigm.
What is one to do when there's a supcomponent somewhere that needs to receive information asynchronously on mount (for example, a sidebar or modal)? It may seem that there is no obvious way to do this.
1.We want to create an app with a subcomponent somewhere that will update itself by making an asynchronous call on mount.
2.In addition, our root app component must house its own Context Provider. A corresponding Context Consumer will be placed in the subcomponent. From this subcomponent, we must be able to update on mount and update the state housed in the Context Provider.
3.We must also be able to access props in the entire component, not just in the render method. Since we typically wrap the Context Consumer in a render, this is not a trivial problem.
tldr; Update root's state after an asynchronous call on the subcomponent.
Let's fromt the end, shall we?
Before we get ahead of ourselves, let's briefly review what a higher-order component is.
An HOC is a function that receives a component as an argument and returns another component (usually an enhanced version of the same component). For example, we may want to add some properties (but no side-effects!) that we defined which the returned component then will have access to.
Well-known examples of HOCs include the
connect() function from
As an example, the React docs provide a straightforward HOC that returns a component that logs its props. I provide it below, with a slight modification to bring it up to date to the current API.
Any component can then be enhanced with this logProps component:
const EnhancedComponent = logProps(InputComponent);.
Let's now get started with the solution. This is forked and modified from the Stackblitz React starter.
AppContext and pass its Provider at the top level in our render. Our
value prop contains one state value
name and a method to modify our name (remember the
value prop is required for the Context Provider and can be any value).
Now before we get to consuming our Context, we need to create an HOC that will allow our
Hello component to consume it in its entirety. For convenience, we'll add it to the same file where our context is created.
This is not a whole lot more complicated than the
logProps. A key difference here is that we are actually wrapping another component (
AppContext.Consumer) into the component that was passed in.
In the Consumer's child we have a function that adds all of the context as props to the Component, via the object spread operator. We then add any extra props that may have been passed directly into the Component.
With this done, our
Name component can be created.
Name enhanced by
withContext(), that is the version that is rendered in
Since we want to fetch some information on mount, we have an async
componentDidMount(), which makes our (fake) API call. Once we receive our response, we call the
setName() action that was passed into Context from the Provider.
This fulfills our conditions set out above.
We have a root component and a subcomponent that makes on update on mount (#1). Our App component wraps a Context Provider around its children, and our subcomponent (Hello/Message) uses a Context Consumer, via our HOC (#2). Last, we have access to our context wherever we like in Hello/Message, not just in
Going Further - Local Context Containers
This is great, but I think we can do better. In my opinion, there is one main problem with the previous solution.
We are mixing API logic with UI concerns. It would be better to have separate areas in our app that can contain these kinds of logic.
Enter Local Context Containers (LCCs).
LCCs are about isolating components or group of components, and wrapping them into container components. Each container gets its own Context Provider. This allows us to group sections of our app into logical sections that should contain shared state.
This is not too different at all from the concept of Container Components in Redux.
This is particularly useful for things such as forms, or in general sections that serve as relatively isolated areas (navbar, modal, sidebar).
Adding a Message Container
We now add a message container with its own Context that will hold our Hello/Name component.
There is nothing conceptually new here. We are defining
MessageContainer which provides that Context, and
withMessageContext which is an HOC that consumes our Context.
With this change, our Hello/Name component can be very small and clean. "Presentational," if you will
Note, again, that we are exporting the enhanced Name component using
Last, we update
Nothing that different here, except for the fact that we are now using
Also, we keep AppContext. It's doing nothing right now (the
value prop in
AppContext.Provider is an empty object), but it could! This is merely to illustrate that we can have multiple contexts at the same time.
Well, that's about it! I hope you found this post a useful illustration of some concepts relating to Context. To cap:
- We can use higher-order components (HOCs) to wrap Context Consumers on any component. This is a nifty and reusable way to allow our entire components access specific Contexts without cluttering them up with Context logic.
- It's not too trivial to implement asynchronous actions using Context. If we want to fetch data asynchronously on mount or on change, there are several ways to do that.
- Local Context Containers (LCCs) is an approach to organizing React applications with Context. It allows us to cordon off different groups of components as children to components that consume the containers' Context.