Getting Started w/ React’s Context API

Perhaps you’ve heard of it and perhaps you know what it is and what it does, but not sure where to start trying it out — let’s cover that for you!

What is the Context API?

“ Context provides a way to pass data through the component tree without having to pass props down manually at every level.” — React Docs

If you’re familiar with Redux or other similar state management systems, then it’s easiest to think of this as React’s minimal version. In Redux you gain access to the universal store or state by using mapStateToProps and mapDispatchToProps which are functions you get from connecting React and Redux together.

Instead of these two functions, you just have to wrap your rendered components and elements in a Consumer with the Context API. And this is done only after you’ve wrapped all components you want to have access to the store with the Provider, just like in Redux — minus the actions, types and dispatchers.

In Redux you would wrap your components — at a high level — with a provider. And then use said two functions to access and manipulate data in the store. With the Context API, you’re essentially doing the same thing. Just think of the consumer as both mapStateToProps and mapDispatchToProps, and that there are no actions, dispatchers or types basically.


The Fundamentals

Many examples online, in blogs and tutorials show this kind where create a context and use the provider and consumer within the same file.

Here we have this basic setup:

index.js
App.js

This is your basic create-react-app setup right, and this is what it currently displays:

That’s some impressive stuff there!

Creating context

But let’s create a context and start using it to access the store (referring it to store since Redux uses the term) to render the same text. First we will create said context:

Here we created a context to use and destructured it to gain access to Consumer and Provider without having to create a variable name for the context and accessing it that way.

The Provider

Next we have to wrap components with the Provider which will allow the inside components to gain access to the store if we wanted them to have access to it. You have to do the same when you use Redux as well.

Notice we are giving the value the App’s state — you can also construct an object inside in-line but that’s bad for performance as React would continue to recreate that object on every render, which is unnecessary and can impact the speed and performance of the app.

Like this, a new object would be recreated each and every time:
<Provider value={{ text: "hello world"}}>

The Consumer

Now how can we access the store instead of using App’s state? We have to also wrap components with the Consumer — by doing so you are giving that or those components direct access to the store. It sounds a lot like what we are doing with the Provider, so what’s the difference? In short, you need the components to wrapped with both the Provider and Consumer. But you selectively give access to the store with the Consumer. If you wanted this component to be able to read from the store, and another not to because it doesn’t need it then you can do so.

When you wrap components with the Consumer, you have to return a callback as such. This is the only way you can successfully render anything without your application breaking. But this way you can gain access to the store instead of the state.

Note I’m surrounding everything with parenthesis and don’t let that trip you up if you’re unfamiliar with it — it’s an arrow function implicitly returning everything inside it but allowing you to write on multiple lines. You could also use curly braces instead and explicitly return the elements with parenthesis afterwards.

Destructuring

This is how you’d do it without destructuring the Provider and Consumer:

Without destructuring:
const StoreContext = React.createContext()
...
<StoreContext.Provider>
<StoreContext.Consumer>
...
</StoreContext.Consumer>
</StoreContext.Provider>

And there you have it — the most basic way of implementing the context API. But what good is this in a larger application with multiple components? Good thing you asked — let’s explore other ways we can use context in applications.


Getting Familiar

Let’s make the store separate and independent from App completely and let’s see how you can also pass down info from the store to other components, just like you would with state.

I created an object called initialState and set that to be the Provider’s value which makes it completely separate from App now. And I also created simple presentational/display components which renders the same thing as previously. The Para component takes a prop and renders that text whereas the Title and Header components only render the same text.

You’ll notice it is rendering the initialState text instead of App’s state’s text:

Look at what we’re rendering in App.js again — we aren’t passing anything or using anything from the store in the Title or Header components. So we clearly don’t need to wrap all of these components at all — we only need to wrap the Para component. This can be done in two different ways:

We can limit the Consumer’s range and the components inside App.js itself, or we can take the Consumer out of App.js and into the Para component. In order to do so, we need to change App.js just slightly for this example. We can no longer export default App because we also need to export the Consumer too.

App.js

*Note how clean the return area for App is now.

Now that we can export multiple things from App.js, we can import them in different locations (if we choose, and we choose to).

index.js
Para.js

Since we got that out of the way, let’s implement the same syntax as we saw in App.js earlier with Consumer but inside the Para component.

Para.js after using Consumer

Great! We’ve secluded the Title and Header components from the Consumer and only included Para because it was the only component which needed information from the store. And you’ll see it still renders the same:

Independence

Take another look at App.js — we completely made the store independent from the App component. We might as well separate it into it’s own file too!

Context.js

I decided to show you also how you could do it without destructuring in code here. We’re exporting both the Provider and Consumer so we can import and use it in other components and files.

For one, we shall import the Provider into index.js to surround App allowing it to be at the most top level in our application. This is good because we won’t have to struggle to move it around later on when we expand our application with more and more components which may potentially need access to the store.

index.js

We have to adjust the location from where we are retrieving the Consumer now that we moved it into it’s own file, Context.js, in the Para component.

Para.js

Remember how we had to change the exports to allow multiple exports out of App.js? Let’s change that back and adjust how we import it into index.js as well.

App.js with the export default again
index.js with the original import accounting for export default
Let’s recap for a second — we managed to separate and make the context independent of any component, strategically placed the Provider at the highest level to prevent any future issues, and we’ve limited the scope of components which have direct access to the store and its information.

Getting Comfortable

This is all pretty wonderful, but I know you’re craving more, and frankly better things! Currently we only have one piece of data in our store, let’s add another and make it a function to change the store’s info with a button.

There’s no correct way to start this off and I think the task is simple enough to visualize, so let’s start off with updating the context and store to have this toggle function which will be invoked on the click of a button.

Context.js with a function to toggle the state/store

If this setState function looks a bit different than usual, it’s because we are using a callback as the argument which gives us access to the previous state and its data. React can bundle up state changes and update state then, so just passing setState an object can be unreliable; if you want accurate state changes/updates, this is the way to do it!

With that out of the way, normally you would just have the return statement with the object of info you want to change state with, but I want to toggle the state to have one of two strings which is why I went with a switch statement. Fun fact, switch statements are supposedly faster than if-else statements! Plus they are cleaner and neater to read.

Off to create our nifty little button then!

Button.js

You probably notice the repetition of having to import the Consumer and wrap a component in it just to gain access to the store — we will cover this later too to keep our code DRY and make our developing lives easier!

What else is there left to do then? You got it — we have to import the Button to App and render it on the page!

App.js with newly created Button component
Updated web app with Button component and context with a function to change the store’s data

But wait! If we click the button, nothing happens! I actually ran into this problem recently when I was tinkering around with the Context API for the first time. You have to think of how we made the function, where it is and the component lifecycle in React.

Image result for react component lifecycle
https://cdn-images-1.medium.com/max/2000/1*cEWErpe-oY-_S1dOaT1NtA.jpeg

The Provider we created is a class and we made the toggling function a method on the class. This method is also included in state/store, so how come nothing happens when we click the button? According to the lifecycle, the component is rendered first and at that moment the functions are still null. We can resolve this in a couple of ways actually.

Constructor

When I learned about React, state, props and everything, I started with always defining the constructor in a class and setting up state inside the constructor. Let’s take a look at how this looks if you’re unfamiliar with it:

Context.js

It’s common practice to always call super inside a class constructor. And when we declare the state, you have to link it to this class with the keyword this. This now works because when the application is run, JavaScript first runs the declarations of functions and variables — which is different from invoking or actioning the functions. And according to the React lifecycle, the constructor in a class is the first function to be actioned and invoked.

ComponentDidMount

This is another lifecycle method and we can use it to solve this issue. The reason this works is because this method is invoked after the component is rendered on the page and the method we defined is no longer null.

Context.js

I could throw a check in there but because the lifecycle doesn’t change and this lifecycle method is only run once when it is rendered, there’s essentially no need; we know the state’s handleBtnClick’s value is null and it won’t be invoked multiple times.

And now it works! Hallelujah!

That seemed like a lot of trouble just to add a function to the store and make it available to other components and trigger it with a button! But you did it, or you understand it now!

Getting Good

Remember how I briefly said we would look into how we can make our coe DRY and our lives easier? Well we are going to take a look at it now! We can use a higher order component to set a component up to read and write to the store. This way if you want 20 components to be able to access the store, you only have to write it once!

I think it will be easier to grasp if we take a look at the end result and then how we got there.

Para.js
Button.js

See how much better that is?!?! It’s much cleaner and you don’t have to go through writing that terrible Consumer BS more than once! This is ideal for large applications because you save so much time and when you have multiple developers working simultaneously, this will greatly reduce little mistakes breaking the app and wasting time looking to fix it.

So let’s go through what a higher order component is and how its magic helped make this a reality!

Simply put, a higher order component is a component which returns another component. It’s great for repetitive cases like this one — whether it’s in regards to methods, properties or syntactical situations.
WithConsumer.js

And that’s that! If you come from an object-oriented background then you can think of this as a type of inheritance with classes. But it might be better to think of it as including a module (I’m thinking Ruby) rather than inheritance per-say.


Recent Updates

Previously you could only read from the store within the render/return scope area of the class/function. But now you can outside of it as well with contextType!

Para.js with contextType

If you’re unfamiliar with what static is, you can think of it as a class method, but it really doesn’t matter — what matters is that if you take a look at the renderText method, you’ll see I have access to the store through this.context! It’s quite brilliant and makes our lives easier and gives us more flexibility with what we can do with the Context API.

*Note I had to change Para from a functional component to a class for this short demonstration.

Unfortunately you can’t just replace the previous implementation with this update. I found that when the store is updated, you’re not subscribed to those updates and the components aren’t being re-rendered. Therefore you have to use this update in conjunction with the previous implementation.

Fortunately if you use a HOC like we did just previously, then you already have access to the store in all areas of the function/class. And it is subscribed to updates and re-renders accordingly!

Para.js

Demo

You can find the sandbox I used for this here.

*I had to update the React and React-DOM versions to get the updates to the Context API to work — just in case you try it on your own sandbox too and it doesn’t work.