Importance of useContext Hook in React

Sep 9, 2021
7 min read

Consider a React app with a single parent component. Child components are nested inside this single parent component. In React, you pass data through props. And the data is passed top-down from one component to another.

Now note that in this scenario, you will not be able to skip any component. That is, if the last component has to access the data, you will be going through each and every component and all the way down to the last one. In this case, you'll have to drill down using each component's prop.

The method is long and laborious and doesn’t seem to fit.

There will always be a possibility of error somewhere in the process. So what we want is a solution in which we don't have to interact with all the components down the line and are easily able to target a particular component.

To discuss the solution available in React, TechFastly presents this article to give a proper explanation about the useContext hook and how a custom React Hook can further ease the use of the useContext Hook.

The answer to your problem is in the React Context API. Using this you can access different levels of the component tree. You can get the same functionality from useContext Hook as well.

This example shows the useContext Context inside of a class component and functional component.

Whenever you are going to use the useContext Hook in React, make sure you pass in the entire Context object, and not just the consumer or provider.

Let's create a Context object in React. And the syntax is:

This EduAppContext object will be passed as an argument to the useContext hook.

Now let me give you an example of a music player to explore. This music player of mine will have a list of songs and the controls will be at the bottom. Now we should know which track is playing and whether the playing track is on play mode or pause mode.

For this, we need React Context API which will store this information in its state. Then the turn of the useContext Hook that will make this state available to both components.

I am creating a new File called MyPlayerContext.js

In this example, the React component is acting as a wrapper. We have set the context. The component is returning the provider in which the two components will be wrapped.

In MyPlayerContext, it is clearly visible that no argument is passed to it. That means that the Context has no default value.

The name of another new React component is MyPlayerProvider which gives us the provider. It also has no initial value.

Because of MyPlayerProvider, the child components will be able to access the value of the Context's value. This provider will return the Context object which will be consumed by other components.

At last, you need to export both the Provider and the Context.

Create a Stateful Context

The Context has no value, and we don't want it to have a static value. We will be giving it a state which will enable it to change its value. Here we will come up with the useState Hook.

MyPlayerContext.js

Above we are passing an array with 2 values. The first index contains an empty object and the second one contains an empty function.

const MyPlayerContext = React.createContext([{}, () => {}]);

useState Hook provides us with the state in which we will store multiple values.

const [state, setState] = useState({});

By putting the state object and setter function in an array, we will pass this array to the value of the Context Provider. That's why, when we created the Context above, we put an empty array and an empty function in it.

<MyPlayerContext.Provider value={[state, setState]}>
{props.children}
</MyPlayerContext.Provider>

We will access the Context state by importing it into the component. After that, we will use the useContext Hook.

Awesome!

Update the Context’s state

Let's move on to App.js. Inside it, we'll import the new MyPlayerProvider component and wrap it around TrackList.

App.js

A new file will contain the TrackList component.

TrackList.js

MyPlayerContext will be imported into this file. As you see MyPlayerContext exports two values, so a name({MyPlayerContext}) has to be given while importing.

setState in our Context will be called on click of a simple button. Here, useContext Hook will provide setState to us.

● To store the value in the state, we are not using the useState hook, rather we are updating the key/value pair using a single state object.

● Because of the single state object, we have to pass the existing object to the new state object, so that the whole object is not overwritten. This is the role of the ..state syntax here.

If you go to your running React app and click the button, the label will change to "Clicked!" when you click the button.

Now Tracks from the Context State

Let me edit MyPlayerContext and pass initial song titles to the Context. I am doing this because my TrackList React component is empty.

In the MyPlayerContext where we are initializing the state object, we will pass an array of tracks to the argument of useState Hook.

Now I'll simply map over new tracks array coming from the Context’s state. The map function will unwind the array and we will have one object for each track, and the key of the object called name.

Track.name will display the track name inside the curly braces in the map function.

TrackList.js

Updating Context from One Place

Whenever we have to use or update the state from the useContext Hook, it is mandatory to import the context. If you want to retrieve and update the state, that's fine, but if there's additional logic coming with state involvement, it will give the component a complicated shape.

Take an example like this to understand a little more: Before clicking the play button on a track, you will see that the track is not already playing and if not you will click the play button.

This logic is very common for any music player app. Many components use this logic. So the logic must be in one place so that multiple components can use it.

Now it's time for custom React Hooks

In Custom React Hook, we add "use" before the name of the hook.

Now we will create a new file useMusicPlayer. Through our useContext hook, we will access the state and setState variables, i.e. we need the context here.

Having done all this, isPlaying value in the togglePlay function, which is basically inside the Context state, will return true or false depending on whether the track is already playing or not.

functiontogglePlay(){setState(state =>({...state, isPlaying:!state.isPlaying }));}

Now we will add functions to play track, play next track, and play the previous track.

At the end of this custom hook, we are returning all the functions along with some variables.

We get all these variables from the useContext Hook which we get from React.

Now that we have all the useful functions in our new custom React Hook, let's edit the TrackList component a bit.

We have allowed the helper function of Hook's to interact with the context state, instead of having to import the key context directly.

Event handlers can easily call functions, our React components are so linear.

onClick={() => playTrack(index)}

The turn of the music player controls, the component for that is the PlayerControls.js. Make this component with your own design.

Also in this component, we are importing useMyPlayer Custom Hook so that we can get all helper functions that interact with the Context state.

These child components must be wrapped in the MusicPlayerProvider or else we will not get the context state. So let's edit App.js.

App.js

In MyPlayerContext.js, make sure you enter default values in the Context state.

You already know that you have to put the mp3 files in the src folder as well as instantiate the Audio object.

So now you need to edit MyPlayerContext and import mp3 files. The useContext Hook is worth mentioning which we can easily access in our custom hook. You have to make the custom React Hook useMyPlayer interact with the audio object as well, and which you can easily do.