Three React components with the first two using the React Context API passing data to one another, avoiding the first child component.

Let’s explore the React useContext Hook by building a music player! I’ll also show you how a custom React Hook can make the useContext Hook easier to use.

A music player built using React and the useContext Hook.
What we’re building in this tutorial.

Before we jump into our code editor, let’s understand the React Context API a little more, and how the useContext Hook helps simplify sharing data with multiple React components.

What is React Context?

Imagine for a moment that you have a React app with a single parent component that contains many levels of child components inside of it.

Now, imagine passing data from the uppermost component all the way down to the last child component.

In React data is passed top-down from one component to another through props.

You’d have to pass that data through each and every component, through their props, until you reach the last child component.

That’s painstaking, tiresome, and prone to errors. This is where Context comes to the rescue.

The React Context API allows you to easily access data at different levels of the component tree, without having to pass data down through props.

I recommend skimming through the official React documentation on Context before continuing.

How Does the useContext Hook Work?

The useContext Hook provides all the same functionality you’d expect from the Context API, just packaged up into a simple to use Hook that you can use inside functional components.

Let’s compare the difference between using Context inside of a Class component to using it inside of a functional component with the useContext Hook.

The example below shows Context being used in a Class component:

import AppContext from './appContext.js'; class Example extends React.Component { static context = AppContext; render() { let value = this.context; ... } }

And below is the same Context object inside of a functional component, using the new useContext Hook:

import AppContext from './appContext.js'; const Example = () => { const context = useContext(AppContext); return ( ... ); }

A Context provides both a consumer and a provider. When using the useContext Hook in React, you have to remember to pass in the whole context object, not just the consumer or provider.

You create a Context object in React by using React.CreateContext, and then passing in an initial value, like so:

const AppContext = React.createContext({ foo: 'bar' });

This AppContext object is what should be passed as an argument into the useContext Hook. Like this:

const context = useContext(AppContext);

What We’re Building

Let’s learn how we can use the useContext Hook in React to help us build a simple Spotify clone !

I’m using the Bulma CSS library and FontAwesome in my examples below. I’ve als created a fresh React app using Create React App. You can do the same, or use an existing React project.

Building a music player is a fantastic example to demonstrate how the useContext Hook works because it has two child components that share the same application state:

  • A list of songs with a play/pause button for each.
  • A set of player controls for playing/pausing, skipping to the next track, and skipping to the previous track.
A music player built in React and showing a song list and music player controls.

Take a look at the screenshot above of what we’re building. The list of songs and the player controls at the bottom both need to know which track is currently playing, and if the track is playing or if it’s paused.

Therefore, we’ll use the React Context API to store this information inside of its state, and then we’ll use the useContext Hook to make it this state available to both components.

Creating the Music Player Context

Create a new file called MusicPlayerContext.js. This will be a React component that acts as a wrapper where we set up the Context, but also return that Context’s Provider for us to wrap our two other components in.

import React from 'react'; const MusicPlayerContext = React.createContext(); const MusicPlayerProvider = (props) => { return ( <MusicPlayerContext.Provider value={}> {props.children} </MusicPlayerContext.Provider> ); } export { MusicPlayerContext, MusicPlayerProvider };

In the code above, we create a new Context called MusicPlayerContext with no arguments passed inside of the parentheses, indicating that there is no default value for the Context.

We also define a new React component called MusicPlayerProvider that returns the MusicPlayerContext’s Provider, again, with no initial value.

The MusicPlayerContext.Provider is what allows child components to access the Context’s value. It provides the Context object for other components to consume.

Finally, we export both the MusicPlayerContext Context and MusicPlayerProvider component. I’ll show you why we’re exporting both the Context as well as the functional component very soon.

Creating a Stateful Context

Our Context doesn’t have a value yet. Rather than giving it a static value, we want to give it the ability to change its value. Therefore, we need to give our Context state.

For this, we’ll use the useState Hook.

import React, { useState } from 'react'; const MusicPlayerContext = React.createContext([{}, () => {}]); const MusicPlayerProvider = (props) => { const [state, setState] = useState({}); return ( <MusicPlayerContext.Provider value={[state, setState]}> {props.children} </MusicPlayerContext.Provider> ); } export { MusicPlayerContext, MusicPlayerProvider };

Let’s walk through the code above.

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

This line creates the Context, as before, but this time we’re passing in an array with two values: an empty object, and an empty function, as the initial value. You’ll see why in just a second.

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

Here, we use the useState Hook to provide us state. We’re storing multiple values inside of this state, so we name the first variable state, and the second variable setState.

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

Finally, here we put the state object and the setter function into an array, and pass that into our Context Provider’s value. This is why we passed in an array with an empty object and an empty function when creating the Context.

All we need to do to access the Context’s state is import it into a component and use the useContext Hook in React!

import { MusicPlayerContext } from "./MusicPlayerContext"; ... const [state, setState] = useContext(MusicPlayerContext);

How great is that?!

Updating the Context’s State

Let’s take our new stateful Context for a spin!

Jump over to App.js and import the new MusicPlayerProvider component. Then, wrap it around a new component called TrackList.

import React from 'react'; import TrackList from "./TrackList"; import { MusicPlayerProvider } from "./MusicPlayerContext"; const App = () => { return ( <MusicPlayerProvider> <div className="container"> <TrackList /> </div> </MusicPlayerProvider> ); } export default App;

You’ll have to create a new file for the TrackList component.

Once you do, jump inside of TrackList.js and import the MusicPlayerContext. Remember, because we’re exporting two values from MusicPlayerContext, you need to use a named import.

To test that our Context state updates, let’s add a simple button with an onClick that calls setState in our Context. We’ll use the useContext Hook to make this available to us, like so:

import React, { useContext } from "react" import { MusicPlayerContext } from "./MusicPlayerContext"; const TrackList = () => { const [state, setState] = useContext(MusicPlayerContext); return ( <button onClick={() => setState(state => ({ ...state, name: 'Clicked!' }))}> {} </button> ) } export default TrackList

If you’ve explored the useState Hook in React before, you may have noticed something different about the setState function above…

setState(state => ({ ...state, name: 'clicked!' }))
  • We’re not using a useState Hook for each value we want to store in state. Instead, we use a single state object and update the key/value pairs.
  • Because we use a single state object, we have to pass the existing state object into the new object while we update it, otherwise the whole object will be overwritten. That’s what the …state syntax does.

Go ahead and save the file, jump over to your running React app and click the button. You should see the button label change to read Clicked!

Displaying a List of Tracks from the Context State

Our TrackList React component is looking a little empty. Why don’t we pass in some initial song titles to our Context?

Open up MusicPlayerContext, find where we’re initializing the state object with the useState Hook, and add an array of tracks to the initial value:

const MusicPlayerProvider = (props) => { const [state, setState] = useState({ tracks: [ { name: 'Lost Chameleon - Genesis', }, { name: 'The Hipsta - Shaken Soda', }, { name: 'Tobu - Such Fun', }, ], }); return ( <MusicPlayerContext.Provider value={[state, setState]}> {props.children} </MusicPlayerContext.Provider> ); };

Back in the TrackList component, let’s map over the new tracks array coming from the Context’s state:

import React from "react"; import { MusicPlayerContext } from "./MusicPlayerContext"; const TrackList = () => { const [state, setState] = useContext(MusicPlayerContext); return ( <> { => ( <div className="box"> <div className="song-title"> {} </div> </div> ))} </> ) } export default TrackList

Because tracks is an array, all we need to do to loop over an array in React is use the map function. Each track is an object with a single key called name.

Displaying the track’s name is as easy as putting inside of curly braces inside the map function, like so:

<div className="song-title"> {} </div>

Updating Context in React from One Place

We have to import context every time whenever we want to use or update the state from the useContext Hook in our React app.

This is fine for doing simple, one line calls like retrieving and updating the state. However, whenever we introduce additional logic involving the state, our components will become much more complex.

Let me give you an example:

When you click the play button on a track, you first have to check whether that track is currently playing before you start playing it, otherwise, you’ll want to pause that track. If it’s not playing, you set that track to play.

The logic for this example might look something like this:

function playTrack(index) { if (index === state.currentTrackIndex) { pauseTrack(); } else { setState(state => ({ ...state, currentTrackIndex: index, isPlaying: true })); } }

This logic is likely very common inside of a music player app and will be used by many components.

Therefore, let’s put all of this common logic in one place for all of our components to use!

Say hello to custom React Hooks!

Using a Custom React Hook to Manage Context

Create a new file called useMusicPlayer.js. Because this is a custom React Hook, we use ‘use’ before the name of the Hook.

If you want to learn how custom React Hooks can make your components much leaner, check out my tutorial on Simplifying Forms with Custom React Hooks.

Next, let’s bring in our Context and get access to the state and setState variables using the useContext Hook in our new custom React Hook.

import { useContext } from 'react'; import { MusicPlayerContext } from "./MusicPlayerContext"; const useMusicPlayer = () => { const [state, setState] = useContext(MusicPlayerContext); }; export default useMusicPlayer;

Finally, create a new function, togglePlay which updates an isPlaying value inside of the Context state to true or false, depending on if it’s already playing.

import { useContext } from 'react'; import { MusicPlayerContext } from "./MusicPlayerContext"; const useMusicPlayer = () => { const [state, setState] = useContext(MusicPlayerContext); function togglePlay() { setState(state => ({ ...state, isPlaying: !state.isPlaying })); } return { togglePlay, } }; export default useMusicPlayer;

While we’re here, let’s go ahead and add functions for playing a track, playing the previous track, and playing the next track:

import { useContext } from 'react'; import { MusicPlayerContext } from "./MusicPlayerContext"; const useMusicPlayer = () => { const [state, setState] = useContext(MusicPlayerContext); // Play a specific track function playTrack(index) { if (index === state.currentTrackIndex) { togglePlay(); } else { setState(state => ({ ...state, currentTrackIndex: index, isPlaying: true })); } } // Toggle play or pause function togglePlay() { setState(state => ({ ...state, isPlaying: !state.isPlaying })); } // Play the previous track in the tracks array function playPreviousTrack() { const newIndex = ((state.currentTrackIndex + -1) % state.tracks.length + state.tracks.length) % state.tracks.length; playTrack(newIndex); } // Play the next track in the tracks array function playNextTrack() { const newIndex = (state.currentTrackIndex + 1) % state.tracks.length; playTrack(newIndex); } return { playTrack, togglePlay, currentTrackName: state.currentTrackIndex !== null && state.tracks[state.currentTrackIndex].name, trackList: state.tracks, isPlaying: state.isPlaying, playPreviousTrack, playNextTrack, } }; export default useMusicPlayer;

Take some time to read through the code above and understand what each of these new functions do.

At the end of the custom Hook, we’re returning all of the functions plus a few helpful variables.

Variables like currentTrackName, trackList, and isPlaying, which are all taken from the useContext Hook that React gives us.

return { playTrack, togglePlay, currentTrackName: state.currentTrackIndex !== null && state.tracks[state.currentTrackIndex].name, trackList: state.tracks, isPlaying: state.isPlaying, playPreviousTrack, playNextTrack, }

Playing a Track from the TrackList Component

Now that we have all of these useful functions in the useMusicPlayer custom React Hook, let’s modify the TrackList component slightly:

import React from "react"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faPlay, faPause} from "@fortawesome/free-solid-svg-icons"; import useMusicPlayer from "./useMusicPlayer"; const TrackList = () => { const { trackList, currentTrackName, playTrack, isPlaying } = useMusicPlayer(); return ( <> {, index) => ( <div className="box"> <button className="button" onClick={() => playTrack(index)}> {currentTrackName === && isPlaying ? <FontAwesomeIcon icon={faPause} /> : <FontAwesomeIcon icon={faPlay} />} </button> <div className="song-title"> {} </div> </div> ))} </> ) } export default TrackList

Rather than importing the Context directly in our component, we’re utilizing the useMusicPlayer Hook’s helper functions to interact with the Context state.

That means our React components are much leaner because we can simply call those functions from event handlers, like so:

<button className="button" onClick={() => playTrack(index)}>

Setting up the Player Controls

Onto the next React component, the music player controls!

Create a new component called PlayerControls.js and add the following code to it:

import React from "react" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faPause, faPlay, faStepBackward, faStepForward} from "@fortawesome/free-solid-svg-icons"; import useMusicPlayer from "./useMusicPlayer"; const Controls = () => { const { isPlaying, currentTrackName, togglePlay, playPreviousTrack, playNextTrack } = useMusicPlayer(); return ( <> <div className="box controls has-background-grey-dark"> <div className="current-track has-text-light"> <marquee>{currentTrackName}</marquee> </div> <div> <button className="button has-text-light has-background-grey-dark" onClick={playPreviousTrack} disabled={!currentTrackName}> <FontAwesomeIcon icon={faStepBackward} /> </button> <button className="button has-text-light has-background-grey-dark" onClick={togglePlay} disabled={!currentTrackName}> {isPlaying ? <FontAwesomeIcon icon={faPause} /> : <FontAwesomeIcon icon={faPlay} />} </button> <button className="button has-text-light has-background-grey-dark" onClick={playNextTrack} disabled={!currentTrackName}> <FontAwesomeIcon icon={faStepForward} /> </button> </div> </div> </> ) } export default Controls

Did you notice the similarities between this component and the previous one?

We’re importing the useMusicPlayer Custom Hook again to bring in all of those useful helper functions that interact with the Context state.

Finally, jump over to App.js and make sure you’re importing these new child components and wrapping them within the MusicPlayerProvider. If they’re not wrapped in the Context provider, they won’t be able to access the Context’s state!

import React from 'react'; import TrackList from "./TrackList"; import PlayerControls from "./PlayerControls"; import { MusicPlayerProvider } from "./MusicPlayerContext"; function App() { return ( <MusicPlayerProvider> <div className="container"> <TrackList /> <PlayerControls /> </div> </MusicPlayerProvider> ); } export default App;

And last but not least, make sure that you’ve added default values to the Context state, inside of MusicPlayerContext.js:

... const [state, setState] = useState({ tracks: [ { name: 'Lost Chameleon - Genesis', }, { name: 'The Hipsta - Shaken Soda', }, { name: 'Tobu - Such Fun', }, ], currentTrackIndex: null, isPlaying: false, }); ...

Making Music with new Audio()

If you jump over to your running React app, you’ll notice that there’s one thing missing….. there’s no sound!

That’s because we’re not playing any! To do that, need some audio files to play. There’s plenty of places to find free audio files online, just Google around.

Grab one or two .mp3 files and come back here when you’ve found them. offers a nice, if a little cheesy, selection of royalty free music.

Drop the .mp3 files into your project’s /src folder.

Now, we need to modify MusicPlayerContext to instantiate a new Audio() object, and import our new audio files:

... import LostChameleon from './LostChameleon.mp3'; import Rock from './TheHipsta.mp3'; import Tobu from './Tobu.mp3'; const MusicPlayerProvider = (props) => { const [state, setState] = useState({ audioPlayer: new Audio(), tracks: [ { name: 'Lost Chameleon - Genesis', file: LostChameleon, }, { name: 'The Hipsta - Shaken Soda', file: Rock, }, { name: 'Tobu - Such Fun', file: Tobu, }, ], ... }); ... }; ...

We’ve got a single Audio object that’s stored in the Context state to play audio from. Thanks to the useContext Hook in React, it’s also super easy to access it in our custom Hook, just like before!

All that’s left is to change our custom React Hook, useMusicPlayer, to interact with the Audio object:

... function playTrack(index) { if (index === state.currentTrackIndex) { togglePlay(); } else { state.audioPlayer.pause(); state.audioPlayer = new Audio(state.tracks[index].file);; setState(state => ({ ...state, currentTrackIndex: index, isPlaying: true })); } } function togglePlay() { if (state.isPlaying) { state.audioPlayer.pause(); } else {; } setState(state => ({ ...state, isPlaying: !state.isPlaying })); } ...

Wrapping Up

So there you have it. After reading this tutorial, you’ll have learned:

  • The benefits of using React Context in long component trees.
  • How to create a new Context in React using React.createContext.
  • How to use the useContext Hook in React to give us access to the closest Context object in our functional components.
  • When to add state to a Context, and how easy it is to retrieve and update the state.
  • The benefits of abstracting out common logic into a custom React Hook.
  • How to play using the Audio() object.

Phew! We covered so much in a single tutorial. I hope all enjoyed reading it as much as I did writing it.

As always, please leave a comment below if you have a question, issue, or if you enjoyed the read!

Avatar photo
👋 Hey, I'm James Dietrich
I work full-time at an AI-based startup out of San Francisco, CA. My true passion is to help others. My tutorials help 150,000+ developers learn React and JavaScript every month. Follow on Twitter, or Github.

💬 Leave a comment

Your email address will not be published.

We will never share your email with anyone else.


Awesome article! The use of useState seems like a great way to streamline some of the other mess I’ve encountered in past attempts to wrap my head around Context. One question though, I’m not clear on the purpose of the arrow function in setState(state => {…state, attr:newVal}). Does it handle something more than just using the spread operator setState({…state, attr:newVal}) would?

Thanks again 🙂

Hi Matt, Thanks! I’m glad you enjoyed it.

To answer your question, the arrow function in setState receives the previous value of the state and returns an updated value. That means we can inject the previous value of the state inside of a new object, and then specific any additional values that we want returned along with the previous value as well.

Great article, the music player is working wonderfully, and what a fun way to learn about the useContext-hook! Thank you.

I’ve found one weird thing, though. I’ve placed the Music Player in the Layout component of a Gatbsy-app, to let it always stay active together with my NavBar and my Footer etc. Change works great from the Controller, playing tracks, changing between them, and pausing. But if I play a track, and navigate to another page (with gatsby-link), the music continues, but the state of the music player changes. So I can’t pause the track or change to another track. I can start another track, but it doesn’t overwrite the old state, which is currently playing the first track.

Any idea why this is? Why the MusicPlayer.Context state is defaulted on routing to another page? The layout-component is imported by all pages of the app, and pages are children within the layout component, in order for the Nav, Music Player and Footer to always stay above the changing content pages

Hi Megard. I’m glad you enjoyed the tutorial, and it’s really cool to hear someone implementing the music player in a Gatsby generated app. I think what’s happening is that even though the context state is changing, the Audio() object isn’t pausing and therefore continues to play music. I’d try forcing an Audio().pause() and seeing if that solves the problem.

this is the BEST explanation of both CONTEXT and useState I’ve read, it help me solve a bunch of other problems with incomplete articles about the same subjects.

You rock, thanks so much, pretty clear tutorial, it really helps me. Much more clear than official docs.

Thank you for the great article!

Do you think hooks+context API is ready to replace state management like Redux or MobX – as I heard that sadly context API is still not as performant as the other options?

Regardless would like your opinion as I would prefer to replace my current state management systems with hooks+context =)



Hey Joe,

You’re welcome, I’m glad you found it useful!

I’m a big fan of the React Context API. It’s not a direct comparison to Redux or MobX, as each one has its pros and cons. I’ve worked on multiple large web apps using the Context API and have been pleasantly surprised with how performant it has been, however.

When I click the next song, if there is any one already playing, it continues to play. Is it some error in my code?

Very nice article. Few issues using this with typescript. Need to change these for context const values: Array = [{}, () => { }];
const context= React.createContext(values);

When adding value it is working for the first time
setState((state: Array) => ({ …state, account: }))

If we call the same line of code again it gives an error –
TypeError: Cannot read property ‘value’ of null

hey james. very nice article. thanks for sharing. for training and to be able to use it on another project, i tried to rebuild your project step by step until the button to setState(name: ‘clicked!’) and unfortunately i do not come further due to this error: “Objects are not valid as a React child (found: object with keys {layout, currentLayout}). If you meant to render a collection of children, use an array instead.” By any chance do you or anybody here has an idea how to solve this? (I already tried to just replace your object in the setState where you declare the names.

Hello Sue, and welcome to the site! Our state is an object, therefore you need to pass an object whenever you setState. Try: setState({ name: ‘clicked!’});. Notice how there are curly brackets inside the setState function.

Thank you!!!

I’ve also found it can be helpful to assign variables like:
`[{someContextVar}] = useContext(SomeContext);`

Also, I’ve found it can be confusing to use `setState` without renaming it. For instance, I prefer to describe the state/context in the method name, especially when modifying it from a child component. In my application, I’m using `setAuth` for instance.

is not good approach since [state, setState] generates a new array every render. So basically it would trigger rerender for every component uses useContext(MusicPlayerContext) even it’s PureComponent / React.memo.

great tutorial, this is my 4th time reading it and copying the text material and finally got it working completely and it works great. thanks

James, thank you for the detail tutorial. It is very thorough and a good demonstration of building custom hooks and `useContext` together.

A quick question after following through the tutorial. What if I have multiple contexts in parent levels, can I specify which context value to retrieve in `useContext`?


oooh, oooh, oooooh! Got this working in my react app, and it’s so nice! Thank you XD

Next thing would be to add controls like a repeat button, a shuffle button, a volume slider, and most importantly of all, a progress bar! So people can jump around the song and see how long it is and when it will be over, etc.

Just throwing those ideas out there for you XD

So I’m trying to the currentTime to display. Doing something like this:

// Get the current time of the currently playing track
function currentTime() {
if (state.isPlaying) {

…but nothing is happening. Trying to wrap my head around this Audio() thing but not fully understanding it yet…

Great article James! Specially the part where you used the examples of TrackList and Controls to show use cases of a custom hook.

Thanks for sharing, this was super helpful and gives you a more complete idea of the Context API than trying to implement Context on a TODO app.

After reading scores of articles on Context API, specifically setting context, this was the first which made the subject comprehensible. Thank you!

I have read practically every article on the web about hooks and context and never found anything even close to this. This actually shows out to create a nice clean and simple api for your business logic using hooks and context-I’ve been looking forever! It’s always a useFetch or useLocalStorage or dom-related examples. This site is fantastic. I don’t know why it never came up in my million google searches. Thanks for the excellent material!