We're planting a tree for every job application! Click here to learn more

How to Use Context in React Applications

Chris M.

31 Jan 2022

7 min read

How to Use Context in React Applications
  • React

React Context API

One of the major issues faced by developers with a React app is prop drilling. It refers to passing data from component A1 to component Z1 by specifying it explicitly through multiple intermediate components. React solves this issue very easily through the Context API.

We’ll look into an example below. Below is our App component;

class App extends Component {
    state = {
        bikes: {
            bike001: { name: 'Bike1', price: 50 },
            bike002: { name: 'Bike2', price: 100 },
            bike003: { name: 'Bike3', price: 150 }
        }
    };
    incrementBikePrice = this.incrementBikePrice.bind(this);
    decrementBikePrice = this.decrementBikePrice.bind(this);

    incrementBikePrice(selectedID) {
        // this method is used to update the state
        const bikes = Object.assign({}, this.state.bikes);
        bikes[selectedID].price = bikes[selectedID].price + 1;
        this.setState({
            bikes
        });
    }

    decrementBikePrice(selectedID) {
        // this method is used to update the state
        const bikes = Object.assign({}, this.state.bikes);
        bikes[selectedID].price = bikes[selectedID].price - 1;
        this.setState({
            bikes
        });
    }

    render() {
        return (
            <div className="App">
                <header className="App-header">
                    <img src={logoSrc} className="App-logo" alt="my logo" />
                    <h1 className="App-title">Welcome to my Bike store !!</h1>
                </header>
                {/* Pass props twice */}
                <Products
                    bikes={this.state.bikes}
                    incrementBikePrice={this.incrementBikePrice}
                    decrementBikePrice={this.decrementBikePrice}
                />
            </div>
        );
    }
}

Below is our Products component

const Products = props => (
    <div className="product-list">
        <h2>List of bikes :</h2>
        {/* Pass props twice */}
        <Bikes
            bikes={props.bikes}
            incrementBikePrice ={props.incrementBikePrice}
            decrementbikePrice ={props.decrementbikePrice}
        />
        {/* We can have serveral other product categories as listed below just as an example : */}
        {/* <Cars /> */}
        {/* <Apparels /> */}
        {/* <FoodItems /> */}
    </div>
);

export default Products;

Below is our Bikes component;

const Bikes = props => (
    <Fragment>
        <h4>Bikes :</h4>
        {/* We can use the props data below */}
        {Object.keys(props.bikes).map(bikeID => (
            <Bike
                key={bikeID}
                name={props.bikes[bikeID].name}
                price={props.bikes[bikeID].price}
                incrementPrice={() => props.incrementBikePrice(bikeID)}
                decrementPrice={() => props.decrementBikePrice(bikeID)}
            />
        ))}
    </Fragment>
);

Finally, we have our bike component as below;

const Bike = props => (
    <Fragment>
        <p>Bike Name: {props.name}</p>
        <p>Bike Price: ${props.price}</p>
        <button onClick={props.incrementPrice}>+</button>
        <button onClick={props.decrementPrice}>-</button>
    </Fragment>
);

The above example demonstrates the issue of prop drilling and it can get really messy if we have nested components.

Let’s see how Context API can help us solve this problem. We’ll first initialize the context.

import React from 'react';
// this is similar to createStore from Redux
// https://redux.js.org/api/createstore
const MyAPIContext = React.createContext();
export default MyAPIContext;

Next, we’ll create the Provider

import MyAPIContext from './MyAPIContext';
class MyAPIContextProvider extends Component {
    state = {
        bikes: {
            bike001: { name: 'Bike 1', price: 500 },
            bike002: { name: 'Bike 2', price: 120 },
            bike003: { name: 'Bike 3', price: 170 }
        }
    };

    render() {
        return (
            <MyAPIContext.Provider
                value={{
                    bikes: this.state.bikes,
                    incrementPrice: selectedID => {
                        const bikes = Object.assign({}, this.state.bikes);
                        bikes[selectedID].price = bikes[selectedID].price + 1;
                        this.setState({
                            bikes
                        });
                    },
                    decrementPrice: selectedID => {
                        const bikes = Object.assign({}, this.state.bikes);
                        bikes[selectedID].price = bikes[selectedID].price - 1;
                        this.setState({
                            bikes
                        });
                    }
                }}
            >
                {this.props.children}
            </MyAPIContext.Provider>
        );
    }
}

Next, we’ll have to make our Provider available to other components. So, we’ll need to wrap our App component with it. Also, we should be able to remove the state/methods, as they are now being defined in MyAPIContextProvider

class App extends Component {
    render() {
        return (
            < MyAPIContextProvider >
                <div className="App">
                    <header className="App-header">
                        <img src={logoSrc} className="App-logo" alt="logo" />
                        <h1 className="App-title">Welcome to my Bike store !!</h1>
                    </header>
                    <Products />
                </div>
            </ MyAPIContextProvider >
        );
    }
}

We’ll now create the Consumer. Here, we’ll need to import the context and we need to wrap our Bike component with it, thus injecting our context into the component. We can use context similar to how we use props. It should have all the values we have shared via MyAPIContextProvider and we can just consume it directly.

const Bikes = () => (
    <MyContext.Consumer>
        {context => (
            <Fragment>
                <h4>Bikes:</h4>
                {Object.keys(context.bikes).map(bikeID => (
                    <Bike
                        key={bikeID}
                        name={context.bikes[bikeID].name}
                        price={context.bikes[bikeID].price}
                        incrementPrice={() => context.incrementPrice(bikeID)}
                        decrementPrice={() => context.decrementPrice(bikeID)}
                    />
                ))}
            </Fragment>
        )}
    </MyContext.Consumer>
);

Now comes our list of Products i.e. Products component. We can see the advantages of using the context API here. This component would get fairly simplified now and it needs to render only a few components.

const Products = () => (
    <div className="products-list">
        <h2>List of Products:</h2>
        <Bikes />
        {/* We can have serveral other product categories as listed below just as an example: */}
        {/* <Cars /> */}
        {/* <Apparels /> */}
        {/* <FoodItems /> */}
    </div>
);

Here, we have compared Redux with Context API. The biggest benefit of using Redux is the central Redux store and that is accessible to any component. Context API also has that functionality provided by default.

Context with React Hooks React has a “useContext” utility which can be used to implement the context in functional components. Let’s take a look at the below example;

import React from 'react';
const SONGS_DATA = [
  {
    id: '1',
    title: 'The Road To Heaven',
    price: 19.99,
  },
  {
    id: '2',
    title: 'Shining like a sun',
    price: 29.99,
  },
];

const App = () => {
  return (
    <div>
      <Songs list={SONGS_DATA} />
    </div>
  );
};

const Songs = ({ list }) => {
  return (
    <ul>
      {list.map((item) => (
        <Song key={item.id} item={item} />
      ))}
    </ul>
  );
};

const Song = ({ item }) => {
  return (
    <li>
      {item.title} - {item.price}
    </li>
  );
};

export default App;

Here we show the user a list of songs where each song has a title and price.

Let’s add context here by using the createContext API.

import React from 'react';
const CcyContext = React.createContext(null);
export { CcyContext };

The createContext function accepts an initial value which is the default if the Provider does not explicitly provide any value i.e. if no “value” prop is assigned. In the below example however, our Provider will provide a static value for our context.

import React from 'react';
import { CcyContext } from './ccy-context';
const App = () => {
  return (
    <CcyContext.Provider value="$">
      <Songs list={SONGS_DATA} />
    </CcyContext.Provider>
  );};

The Context object here exposes our Provider component which is used at the top-level i.e. in the App component of our React app and provide the context to all our child components below. Thus, we do not pass the value via props. Instead, the value is passed via context. In addition, the Context object exposes a Consumer component that can be used in all the child components that want to access the context.

const Song = ({ item }) => {
  return (
    <CcyContext.Consumer>
      {(ccy) => (
        <li>
          {item.title} - {item.price} {ccy}
        </li>
      )}
    </CcyContext.Consumer>
  );
};

The above is a very basic usage of React Context API where we have only one top-level Provider and Consumer component in a child component without Hooks. There can be more than one child as a Consumer component. Let’s now migrate to use the useContext hook.

const Song = ({ item }) => {
  const ccy = React.useContext(CcyContext);

  return (
    <li>
      {item.title} - {item.price} {ccy}
    </li>
  );
};

“useContext” hook accepts the Context as a parameter to get the value from it. We can see that using the Hook instead of Consumer component makes our code much more readable and we don’t need to introduce a separate intermediate component.

In the example seen earlier, the context is a static value. However, for most practical examples, the context will be used to pass a stateful value. Let’s say that the user wants to update the currency and wants to see the corresponding symbol.

const App = () => {
  const [ccy, setCcy] = React.useState('€');
  return (
    <CcyContext.Provider value={ccy}>
      <button type="button" onClick={() => setCcy('€')}>
        Euros
      </button>
      <button type="button" onClick={() => setCcy('$')}>
        USD
      </button>
      <Songs list={SONGS_DATA} />
    </CcyContext.Provider>
  );
};

The inline event handlers will update the value when any of the buttons is clicked. Since there is a re-render that takes place after the state is updated, the updated value gets passed in via the Provider component to all the child components that display it as a dynamic value. Let’s also update our example to also have the amount converted, as we have only updated the symbol as of now.

const CCYS = {
  Euro: {
    symbol: '€',
    label: 'Euros',
  },
  USD: {
    symbol: '$',
    label: 'USD',
  },
};
const App = () => {
  const [ccy, setCcy] = React.useState(CCYS.Euro);
  return (
    <CcyContext.Provider value={ccy}>
      <button
        type="button"
        onClick={() => setCcy(CCYS.Euro)}
      >
        {CCYS.Euro.label}
      </button>
      <button
        type="button"
        onClick={() => setCcy(CCYS.USD)}
      >
        {CCYS.USD.label}
      </button>
      <Songs list={SONGS_DATA} />
    </CcyContext.Provider>
  );
};
...
const Song = ({item }) => {
  const ccy = React.useContext(CcyContext);
  return (
    <li>
      {item.title} - {item.price} {ccy.symbol}
    </li>
  );
};

Let’s also use a dictionary for rendering the buttons which update the context's value as seen below.

const CCYS = {
  Euro: {
    symbol: '€',
    label: 'Euros',
  },
  USD: {
    symbol: '$',
    label: 'USD',
  },
};

const App = () => {
  const [ccy, setCcy] = React.useState(CCYS.Euro);
  return (
    <CcyContext.Provider value={ccy}>
      {Object.values(CCYS).map((item) => (
        <button key={item.label} type="button" onClick={() => setCcy(item)} >
          {item.label}
        </button>
      ))}
      <Songs list={SONGS_DATA} />
    </CcyContext.Provider>
  );
};

Next, we would extract the buttons into separate components and that would clean up the App component.

const App = () => {
  const [ccy, setCcy] = React.useState(CCYS.Euros);

  return (
    <CcyContext.Provider value={ccy}>
      <CcyButtons onChange={setCcy} />
      <Songs list={SONGS_DATA} />
    </CcyContext.Provider>
  );
};
const CcyButtons = ({ onChange }) => {
  return Object.values(CCYS).map((item) => (
    <CcyButton key={item.label} onClick={() => onChange(item)}>
      {item.label}
    </CcyButton>
  ));
};

const CcyButton = ({ onClick, children }) => {
  return (
    <button type="button" onClick={onClick}>
      {children}
    </button>
  );
};

Finally, we’ll use the conversion rate we get from the context object to display formatted amount to the user.

const CCYS = {
  Euro: {
    code: 'EUR',
    label: 'Euros',
    conversionRate: 1, // this is the base rate for conversion
  },
  USD: {
    code: 'USD',
    label: 'USD',
    conversionRate: 1.25,
  },
};
...
const Song = ({ item }) => {
  const ccy = React.useContext(CcyContext);

  const price = new Intl.NumberFormat('en-US', {
    style: 'ccy',
    ccy: ccy.code,
  }).format(item.price * ccy.conversionRate);

  return (
    <li>
      {item.title} - {price}
    </li>
  );
};

Best Practices


There are a few best practices that can be followed when using the useContext. When we create a new context, we always start as below; ``` import React from 'react'; const CcyContext = React.createContext(null); export { CcyContext }; ```

We can also provide a custom hook for accessing the context as below;

import React from 'react';
const CcyContext = React.createContext(null);
const useCcy = () => React.useContext(CcyContext);
export { CcyContext, useCcy };

We can then use the custom context hook directly without having to call “useContext” as an intermediary.

import React from 'react';
import { CcyContext, useCcy } from './ccy-context';
...
const Song = ({ item }) => {
  const ccy = useCcy();
  const price = new Intl.NumberFormat('en-US', {
    style: 'ccy',
    ccy: ccy.code,
  }).format(item.price * ccy.conversionRate);
  return (
    <li>
      {item.title} - {price}
    </li>
  );
};

We can also expose a Higher-Order component (HOC) to use context in 3rd party like Styled Components

import React from 'react';
const CcyContext = React.createContext(null);
const useCcy = () => React.useContext(CcyContext);
const withCcy = (Component) => (props) => {
  const ccy = useCcy();
  return <Component {...props} ccy={ccy} />;
};
// if ref is used
//
// const withCcy = (Component) =>
//   React.forwardRef((props, ref) => {
//     const ccy = useCcy();
//     return <Component {...props} ref={ref} ccy={ccy} />;
//   });

export { CcyContext, useCcy, withCcy };

Also, similar to the custom context hook, we can also have a custom Provider component as below;

import React from 'react';
const CcyContext = React.createContext(null);
const useCcy = () => React.useContext(CcyContext);
const CcyProvider = ({ value, children }) => {
  return (
    <CcyContext.Provider value={value}>
      {children}
    </CcyContext.Provider>
  );
};

export { CcyProvider, useCcy };

Now that the CcyContext itself is not exported. Instead, we export the custom Provider component which gets consumed in the App component and that receives the “stateful” value as seen below;

import React from 'react';
import { CcyProvider, useCcy } from './ccy-context';
..
const App = () => {
  const [ccy, setCcy] = React.useState(CURRENCIES.Euro);

  return (
    <CcyProvider value={ccy}>
      <CcyButtons onChange={setCcy} />
      <Songs list={SONGS_DATA} />
    </CcyProvider>
  );
};

Now everything is encapsulated in the custom context hook and custom Provider component. Thus, we can see that the React Context API provides a lot of benefits in terms of simplifying our code and also gives global access to the context object across all the components.

Did you like this article?

Chris M.

I love doing frontend development and am a fan of cutting edge frameworks like React, Angular, Backbone, etc

See other articles by Chris

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub