React Context API

Context API is a way to produce variables that can be passed around without having to pass props down manually at every level.

Syntax is React.createConetxt(Provider, Consumer). It returns a provider and consumer. A provider provides the state to its children. It will be the parent of all the components and store all. Consumer is a component that consumes and uses the state.

Let’s explore how we would handle common problems without the React Context API:

App.js

class App extends Component {
    state = {
        cars: {
            car01: { name: 'Honda', price: 100 },
            car02: { name: 'BMW', price: 150 },
            car03: { name: 'Mercedes', price: 200 }
        }
    };
    incrementCarPrice = this.incrementCarPrice.bind(this);
    decrementCarPrice = this.decrementCarPrice.bind(this);

    incrementCarPrice(selectedID) {
        // a simple method that manipulates the state
        const cars = Object.assign({}, this.state.cars);
        cars[selectedID].price = cars[selectedID].price + 1;
        this.setState({
            cars
        });
    }

    decrementCarPrice(selectedID) {
        // a simple method that manipulates the state
        const cars = Object.assign({}, this.state.cars);
        cars[selectedID].price = cars[selectedID].price - 1;
        this.setState({
            cars
        });
    }

    render() {
        return (
            <div className="App">
                <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <h1 className="App-title">Welcome to my web store</h1>
                </header>
                {/* Pass props twice */}
                <ProductList
                    cars={this.state.cars}
                    incrementCarPrice={this.incrementCarPrice}
                    decrementCarPrice={this.decrementCarPrice}
                />
            </div>
        );
    }
}

ProductList.js

const ProductList = props => (
    <div className="product-list">
        <h2>Product list:</h2>
        {/* Pass props twice */}
        <Cars
            cars={props.cars}
            incrementCarPrice={props.incrementCarPrice}
            decrementCarPrice={props.decrementCarPrice}
        />
        {/* Other potential product categories which we will skip for this demo: */}
        {/* <Electronics /> */}
        {/* <Clothes /> */}
        {/* <Shoes /> */}
    </div>
);

export default ProductList;

Cars.js

const Cars = props => (
    <Fragment>
        <h4>Cars:</h4>
        {/* Finally we can use data */}
        {Object.keys(props.cars).map(carID => (
            <Car
                key={carID}
                name={props.cars[carID].name}
                price={props.cars[carID].price}
                incrementPrice={() => props.incrementCarPrice(carID)}
                decrementPrice={() => props.decrementCarPrice(carID)}
            />
        ))}
    </Fragment>
);

Car.js

const Cars = props => (
    <Fragment>
        <p>Name: {props.name}</p>
        <p>Price: ${props.price}</p>
        <button onClick={props.incrementPrice}>&uarr;</button>
        <button onClick={props.decrementPrice}>&darr;</button>
    </Fragment>
);

Let’s explore how we would handle common problems with the React Context API:

1. Initialize the Context

First, we need to create the context, which we can later use to create providers and consumers.

MyContext.js

import React from 'react';

const MyContext = React.createContext();

export default MyContext;

2. Create the Provider

Once that’s done, we can import the context and use it to create our provider, which we’re calling MyProvider. In it, we initialize a state with some values, which we can share via value prop our provider component.

MyProvider.js

import MyContext from './MyContext';

class MyProvider extends Component {
    state = {
        cars: {
            car01: { name: 'Honda', price: 100 },
            car02: { name: 'BMW', price: 150 },
            car03: { name: 'Mercedes', price: 200 }
        }
    };

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

To make the provider accessible to other components, we need to wrap our app with it. While we’re at it, we can get rid of the state and the methods because they are now defined in MyProvider.js.

App.js

class App extends Component {
    render() {
        return (
            <MyProvider>
                <div className="App">
                    <header className="App-header">
                        <img src={logo} className="App-logo" alt="logo" />
                        <h1 className="App-title">Welcome to my web store</h1>
                    </header>
                    <ProductList />
                </div>
            </MyProvider>
        );
    }
}

3. Create the Consumer

We’ll need to import the context again and wrap our component with it which injects the context argument in the component. Afterward, it’s pretty straight forward. We use context, the same way we would use props. It holds all the values we’ve shared in MyProducer, we just need to use it.

Cars.js

const Cars = () => (
    <MyContext.Consumer>
        {context => (
            <Fragment>
                <h4>Cars:</h4>
                {Object.keys(context.cars).map(carID => (
                    <Car
                        key={carID}
                        name={context.cars[carID].name}
                        price={context.cars[carID].price}
                        incrementPrice={() => context.incrementPrice(carID)}
                        decrementPrice={() => context.decrementPrice(carID)}
                    />
                ))}
            </Fragment>
        )}
    </MyContext.Consumer>
);

Then we wrap the Cars.js component inside the ProductList.js component. The component is simplified because it only needs to render a few components.

const ProductList = () => (
    <div className="product-list">
        <h2>Product list:</h2>
        <Cars /> 
    </div>
);

Notes:

1. What is the context in React?

Ans: React's context allows us to share information to any component, by storing it in a central place and allowing access to any component that requests it. Usually we are only able to pass data from parent to child via props.

2. What is a provider?

Ans: The provider acts as a delivery service. When a consumer asks for something, it finds it in the context and delivers it to where it's needed.

3. What is a consumer?

Ans: A consumer is where the stored information ends up. It can request data via the provider and manipulate the central store if the provider allows it.

15