Skip to main content

Command Palette

Search for a command to run...

React global state management with Context API (no Redux)

Published
6 min read
React global state management with Context API (no Redux)
M

I build front end applications for EVM based blockchain protocols. You can contact me on my LinkedIn profile for business inquiries => https://www.linkedin.com/in/murat-can-y%C3%BCksel-2b1347119/

In my last project, I had to share state between many components. Most of them didn't share a common parent, so passing state with props and a callback function was not an option, also it would be inconvenient to do so. Therefore I used React's Context API to create a global state and share it between any component I wished. In this tutorial, I'll show how to accomplish that.

Before starting, I must give credit to Dev Ed for this enlightening tutorial. I gained and used lots of knowledge from this video. Developers who prefer watching videos may stop reading and click the following link https://www.youtube.com/watch?v=35lXWvCuM8o&t=1790s it is the same concept with slightly different examples.

Note that the example I'll give here is quite basic and React Context API is suggested to be used for more complex instances.

Creating components to work with

To start with, I create 4 components apart from App.js. These components are: -DataOne.js -DataTwo.js -Display.js -DataProvider.js

So there are two components with some data in them, and a component that displays the data sent by those two components. A provider component exists to ensure that the state can be shared smoothly.

Let's start with the provider component.

Provider component

Check the following snippet out:

import React, {useState, createContext} from 'react'

//note that we don't use export default here
//create context here, use context in others

//this DataContext will be shared by all the components 
export const DataContext= createContext([]);

//this is our provider
export const DataProvider=(props)=>{

    const [data, setData]= useState([])

    return(
        <div>
<DataContext.Provider value={[data,setData]}>

{props.children}

</DataContext.Provider >

        </div>
    )

}

What's going on here? I import useState and createContext hooks from React, as you see they're inbuilt. As I gave in the comments, I don't use "export default" here since there are more than one function to be exported.

I invoke the createContext hook in DataContext constant. Note that you can give whatever name you wish in lieu of DataContext. I specify that the context is an array for my future use. This is the context that I will be calling in other components with the useContext hook. We'll look at that in a minute.

Next, I declare the provider in DataProvider. This function is the provider, which means that it will contain and provide the necessary data with other components. It can be seen that I pass "props" inside brackets and use {props.children} in the return statement. I also declare a useState hook, and give it as the provider's value. What do all of these mean?

In order for the provider to provide data with a certain component, that component must be introduced to the provider. There are two ways I know of how to do that: Either you list all the components you wish to share state in between like so :

<DataContext.Provider value={[data,setData]}>

<Display.js/>
<DataOne.js/>
<DataTwo.js>

</DataContext.Provider >

or you use {props.children} in the case that you need lots of components to share state. I'll show how to enable this in the next section. But before that, I want to stress that the value given to is the data that will be shared across components. If I gave "Hello, world!" as value, like so <DataContext.Provider value="Hello, world!"> all the components I specify would share this single string. In my case, I want the data to be dynamic, so I use a useState hook.

Wrapping components to share state with each other

Check out this snippet of App.js:

import React from "react"
import Display from "./Display"
import DataOne from "./DataOne"
import DataTwo from "./DataTwo"
import {DataProvider} from "./DataProvider"

function App() {
  return (
    <div>
      <DataProvider>    
        <DataOne />
        <DataTwo />
        <Display />
      </DataProvider>

    </div>
  );
}

export default App;

Here I just import the components I wish to share state between, plus {DataProvider} from the provider component. See that the import is in curly brackets because there is more than one function to be imported in that component, and I only need the DataProvider function here.

Then, I list all the components I want to share state between inside the and I'm good to go. Now DataOne.js, DataTwo.js and Display.js will share data.

Now let's create the other two components that'll send the data.

Send data between components

Check this snippet from DataOne.j out:

import React, {useState, useContext} from 'react'
import { DataContext } from './DataProvider'

// using curly brackets bcs we have more than one export

export default function DataOne() {

    const [state,setState]= useState("Data coming from DataOne.js")

    const [data,setData]= useContext(DataContext)

    const addDataOne = () =>{
        setData([...data, state])
    }

    return (
        <div>
            <button onClick={addDataOne}>Click to add data from DataOne</button>

        </div>
    )
}

So, I import useState and useContext hooks from React. Attention!=> in DataProvider.js I imported the hook "createContext", here I import "useContext" because I already created my context, now I will use it. Then I declare the state and give it a string of "Data coming from DataOne.js".

The important part here is I declare a useContext hook in a similar manner to useState hook, and pass it the DataContext from the provider component. Note that DataContext in DataProvider.js was this one:

export const DataContext= createContext([]);

In the following, I create a button that will add the state into the context array with Javascript spread operator. Now, whenever I click on that button, the string "Data coming from DataOne.js" will be added to my context and will be available to any of the components the provider has access to.

Now I do the same for DataTwo.js, except I change names accordingly:

import React, {useState, useContext} from 'react'
import { DataContext } from './DataProvider'

// using curly brackets bcs we have more than one export

export default function DataTwo() {

    const [state,setState]= useState("Data coming from DataTwo.js")

    const [data,setData]= useContext(DataContext)

    const addDataTwo = () =>{
        setData([...data, state])
    }

    return (
        <div>
            <button onClick={addDataTwo}>Click to add data from DataTwo</button>

        </div>
    )
}

Using the data

In Display.js, I write the following code:

import React, {useState, useContext} from 'react'
import { DataContext } from './DataProvider'

export default function Display() {
    const [data,setData] = useContext(DataContext)


//here map is using regular brackets (), not curly brackets.
    const mappedData= data.map((item=>(
        <li>{item}</li>

    )))
    console.log(mappedData)


    return (
        <div>
            <ul>
     {mappedData}


            </ul>
        </div>
    )
}

I import {DataContext} in curly brackets from the provider, and {useState, useContext} hooks from React as I did in DataOne.js and DataTwo.js, declare the context with the useContext hook, then simply map the array into a list so that whenever I click on one of the buttons, their respective components will send their state to the global state stored in DataProvider.js, and in turn, the provider will provide the data with all the components I specified. Therefore, with each click, a string item will be added to the array to be displayed on the page. Like so:

contextapi.png

Conclusion

Context API is an easy and no-hassle way for developers who wish to share data between components without using a third party library like Redux.

I hope I was able to help someone out.

Happy coding!

C
CapsCode4y ago

I totally agree with this line *Context API is an easy and no-hassle way for developers who wish to share data between components without using a third party library like Redux.*

But sometimes it depends on the data as well that which type of data and how dynamic is the data & accordingly we should choose the 3rd party library.

Thanks

1
C

As a React newbie, which one's better? Or it depends on the situation?

Should one use the Context API or Redux?

1
M

Hello Catalin, thank you for your comment. Unfortunately, I'm a React newbie and I don't know Redux, so I can't directly answer this question.

But maybe this fact answers something at least. I also heard that Redux is a bit more complicated (which generally means can be used in more complex settings) but I don't know really.

Do you know Redux? If so, what do you think? I'll learn it next for sure, everyone is talking about it :)

A

It depends on the project both work really well. The Context API is probably better for medium sized projects and Redux is better for large scale projects.

Redux is a 3rd party library whereas the Context API is built in so you have to install lots of dependencies which will increase the bundle size. Also Redux has tons of boilerplate and a long setup process because you need to build the store and setup the actions from scratch.

The Context API has significantly less boilerplate so the setup is going to be quicker. However Redux has an extension for dev tools in the browser which makes it easy to see your state and the changes that are made in real time.

4
M

thanks for clarification Andrew! Andrew Baisden

1

More from this blog

Murat Can Yüksel's Blog

26 posts