Getting Started w/ React’s Context API

Paul Ly
Paul Ly
Nov 22, 2018 · 12 min read

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!

Image for post

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

Image for post

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:

Image for post
index.js
Image for post
App.js

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

Image for post

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:

Image for post

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.

Image for post

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.

Image for post

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.

Image for post

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:

Image for post

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:

Image for post

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.

Image for post
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).

Image for post
index.js
Image for post
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.

Image for post
Para.js after using Consumer
Image for post

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:

Image for post

Independence

Image for post

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!

Image for post
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.

Image for post
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.

Image for post
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.

Image for post
App.js with the export default again
Image for post
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.

Image for post
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!

Image for post

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!

Image for post
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!

Image for post
App.js with newly created Button component
Image for post
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:

Image for post
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.

Image for post
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!

Image for post

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.

Image for post
Para.js
Image for post
Button.js
Image for post

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.

Image for post
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!

Image for post
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!

Image for post
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.

Image for post

Data Driven Investor

from confusion to clarity not insanity

Sign up for DDI Highlights

By Data Driven Investor

In each issue we cover all things awesome in the markets, economy, crypto, tech, and more! Take a look

Create a free Medium account to get DDI Highlights in your inbox.

Paul Ly

Written by

Paul Ly

Full-Stack Web Developer - Former JPM Analyst, ESL Teacher, and Expat

Data Driven Investor

from confusion to clarity not insanity

Written by

Paul Ly

Full-Stack Web Developer - Former JPM Analyst, ESL Teacher, and Expat

from confusion to clarity not insanity

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store