In a previous post, I wrote about dependency inversion using render props. If you don’t know about the render props pattern, I would recommend checking that one out first.
To summarize, we can use render props to extract the state management responsibilities from a class that displays an alert.
AlertManager manages the state of the alert and exposes its state through render props.
Now, let's say I want to display an alert only in one place in my application, like a flash message in the header. If I want to set the state of the alert from a component nested deep in the component tree, I may need to pass the
showAlert prop through several other components first, which would be cumbersome and difficult to change.
This is a use case where we typically reach for Redux, Flux, or some sort of global state management. But, as of React 16.3, there is another way!
What is Context?
Context is a new React feature that allows you to share state between components. For example, let’s say I want to make alerts available in the global state. By wrapping my components in my alert context, I can access the context regardless of whether the component is a direct child of the context.
While Context can be used for global state, I can use it anywhere in an application to share state between its children, regardless of their place in the component tree.
How do I use this?
In the first example, we extracted an
AlertManager that uses render props to add or display an alert. We can use Context in the same way to manage state. First, we need to create a Context and a Provider.
Now, I can use
AlertContext to create consumers. Notice how both of these classes can now access the same context to share state.
Also notice how I have separated my responsibilities, allowing one component to be responsible for displaying my application’s alert, and one for creating the event that shows an alert.
Why should I use this?
First of all, if you are considering whether to use Context or Redux, it’s not necessarily an either-or question. While it helps to be consistent in your choice of tools, they’re not mutually exclusive. If you have an application that uses Redux, you can also use Context. But if you’re considering one or the other, in my opinion, the Context API provides a lot of the same functionality as Redux with some additional benefits.
Familiarizing yourself with Redux involves a learning curve. It is a powerful tool, but if you only need simple shared state such as application alerts or a logged-in user, context is a simpler place to start. You can set application state using a simple
setState function rather than a web of actions and reducers.
Though it is generally less complex, its lack of complexity is also one of its downsides. For example, if I need to manage dozens of pieces of state, using dozens of nested Context providers might get cumbersome in my views. This would be a better case for Redux.
Since context is built in to the React API, it is easy to test. I am using React Testing Library here instead of Enzyme, because it will render components using the real
ReactDOM API rather than attempting to mock out context.
Because context uses render props instead of a higher-order function, in my opinion, it is easier to see which props are getting passed in to our components, and where they are coming from. Redux functions like
mapDispatchToProps become well-known to anyone who is well-versed in Redux, but are less readable for someone reading your code for the first time.
In the end, it’s not always a straightforward decision when deciding which state management tool to use for your software. There are lots of factors that go into this decision, and this is just another tool to consider. My team has had success using the React Context API, and I would encourage you to learn more about it!