December 29, 2020

Stateful Provider Pattern

In my quest to find alternatives to redux – and other general-purpose state management libraries in react – I've been tinkering with react's Context API. When combined with the useState or useReducer it produces some powerful effects that can serve as a direct replacement for redux if your needs aren't too complex.

One thing I've struggled with when using this pattern is that it doesn't have a name. I found myself trying to describe it as "React context mixed with state" which still doesn't do justice to this pattern. So, I gave it a name! I call it the Stateful Provider Pattern and you should too!

As mentioned, the Stateful Provider Pattern is a combination of react's Context API and either the state or reducer hooks. To keep this article as focused as possible I'm only going to cover the useState hook. Let's break down these two elements to see how they can be used together to create something powerful.

The Context API

The most recent iteration of the Context API (introduced in 16.3) is one of those things that can be very confusing if you're new to the concept. It helps me to think of it as a variant of the Observer Pattern popularized by the classic Gang of Four book. Don't feel like you need to know those things before mastering the Stateful Provider Pattern though. The links are purely for reference. Don't worry if you're unfamiliar with the Observer Pattern, you can think about the Context API like this:

A single source of truth (called a Provider) is in charge of storing one or more values. Multiple components that want to know about these values connect to the provider (with the useContext hook) so they can access its values. These components are considered consumers of the Provider.

When you create a new context (with the createContext method) you get back two components. A Provider and a Consumer. Under the hood, react keeps track of their relationship so they can always find each other.

Provider

The Provider accepts a prop called value and also accepts children. value can be anything from a boolean to a highly-complex object. children is just like children anywhere else in react with one very important difference. Any Consumer components found among the children of a Provider will receive the closest Provider when accessing its context. This is not normally something you need to worry about, but can be a factor on occasion.

Consumer

The Consumer exists solely to access the value from its Provider. In fact, most of the time you won't even see the word Consumer in when using the Context API because react provides you with the useContext hook to do the heavy lifting for you. Going forward, I will use the term useContext instead of Consumer since you'll be using that hook to access the provider instead of working with the Consumer component directly.

Context API as Dependency Injection

Another thing that has helped me wrap my head around the Context API is to think of it as a dependency injection mechanism. A value (the dependency) is set on the Provider and it is injected into the consumers by the useContext hook. This can be very useful when you have a value that is not going to change. Great examples of this are the <ThemeProvider> from Styled Components or if your app keeps track of user settings which are kept somewhere that can't easily be imported (e.g. localStorage).

The useState Hook

In it's simplest form the useState hook holds a single value and provides a method for overwriting this value. In examples, you'll see these represented as a tuple with 2 elements called state and setState where state is our value and setState is the function to overwrite the state. You do not need to call them state and setState. You can call them anything you want; it's recommended to make sure they follow the {thing} and set{thing} pattern, though. This helps future readers of your code understand your intent.

The value stored in state can be anything from a boolean to a highly-complex object. Does that sound familiar? It should, because that's the exact description of the value prop for a Provider. Coincidence? I think not!

Stateful Provider Pattern

Now that we know a bit more about the Context API and the useState hook, it's time to talk about how we weave them together into a Stateful Provider. Thinking back to the section about dependency injection, what if the dependency isn't static? What if it needs to change over time? The Context API doesn't offer anything to solve this problem directly. You can, of course, use some form of state management above the Provider to allow the value to be changed. Let's explore that a bit with a working example. For our purposes, we are focusing on App.js in the examples below, but I encourage you to click through to see the entire application.

In this demo, we use the Context API to hep other components determine if a user is authenticated. But it's a bit messy because the code to determine if the user is authenticated is kept separate from the code to update the users authentication status. This means we have to important code from multiple places in the app which raises the complexity of our code needlessly. Wouldn't it be great if we could have the authentication status and the updater function come from the same spot? Well, we can. That's one of the many possibilities when using a Stateful Provider. Let's rearrange the previous example into a Stateful Provider to see how much easier it is to accomplish the same effect.

Pay extra attention to the differences in App.js between these two examples. The second example that uses a Stateful Provider has moved all of the logic for managing the state of the user into a single file. This change produces a much cleaner outcome that is easier to read. It also makes future changes to this behavior much simpler since you'll only need to make changes in a single file.

Conclusion

In this article you learned about the Stateful Provider Pattern and how it can be used to create a Stateful Provider in react by combining the Context API with the useState hook. You saw a demo of how to accomplish the same thing without a Stateful Provider and why using a Stateful Provider is an improvement.

If you have additional questions or comments on this pattern you should connect with me on Twitter. I'm interested to hear what you think!