How React Context can come to the rescue if you don’t know Redux

How React Context can come to the rescue if you don’t know Redux

  • 2019-03-15 03:39 AM
  • 228

How React Context can come to the rescue if you don’t know Redux

React has become a fuzzword in front end development over the past few years. React is getting stronger and stronger with the new updates rolling out. This article will help you learn about React Context, which is a pure blessing for developers struggling with Data management between multiple components.

Wait what does REDUX do?

Redux is an open-source JavaScript library for managing application state. It is most commonly used with libraries such as React or Angular for building user interfaces.

In short, Redux helps to maintain a global state which can be accessed throughout the application.

Here is how typical redux works:

Whoa whoa, that escalated quickly. What just happened? There are a lot of concepts to learn about in redux like Store, Action, and Reducers. But there is no need to learn all these things. You can do similar things with React Context. Wondering how?

Introducing React Context

What is context?

Context provides an awesome way to pass data through the component tree without having to pass props down manually at every level in your application.

Until React 15, you could only make use of props to pass the values from the parent to the child component. But there is a lot beyond that: what do you do when you need to pass data between pages (different routes) within the application?

You have to either make use of redux or local storage, using cookies within the browser. Context solves all those problems. Context provides a way to share values like these between the components without having to explicitly pass a prop through every level of the tree within the application.

Context has 3 main blocks:

  1. A context Object
  2. A context Provider
  3. A context Consumer

Let’s try to solve a real-world scenario with context. Assume we want to create an application where products can be added to a cart. There are two pages (routes): one which displays the list of products, and the other which displays items in your cart.

If you were using only react without redux, it would become difficult to manage the state. We will look into how we can use context to manage the global state.

Live Demo

The entire source code can be found here.

As you can see in the about example, there are two different components and the data has to be shared among both the components — a kind of global data.

Context Object

import React from 'react'

React.createContext({}) // argument is the default starting value

context.jsx

You can define the Context object in a separate file or right next to a component in a component file if you wish. Having multiple contexts within the component is also possible. Thus you can store any data you want in the context object which will be accessed globally in your application.

import React from 'react';

export default React.createContext({
  products: [
    { id: 'p1', title: 'React 16 Sticker + T-shirt', price: 29.99 },
    { id: 'p2', title: 'Vue.js T-shirt', price: 9.99 },
    { id: 'p3', title: 'Angular T-shirt', price: 8.99 },
    { id: 'p4', title: 'JS Notebook', price: 2.99 }
  ],
  cart: [],
});

Create Context.jsx

I’ve added Products and cart in the context object. Now it’s time to supply this context to all the components in the application which need to access this data.

Context Provider

The provider provides the values in all the components within the application that needs to access this context object.

import React, { Component } from 'react';

import ShopContext from './shop-context';

class GlobalState extends Component {
  state = {
    products: [
      { id: 'p1', title: 'React 16 Sticker + T-shirt', price: 29.99 },
      { id: 'p2', title: 'Vue.js T-shirt', price: 9.99 },
      { id: 'p3', title: 'Angular T-shirt', price: 8.99 },
      { id: 'p4', title: 'JS Notebook', price: 2.99 }
    ],
    cart: []
  };

  render() {
    return (
      <ShopContext.Provider
        value={{
          products: this.state.products,
          cart: this.state.cart
        }}
      >
        {this.props.children}
      </ShopContext.Provider>
    );
  }
}

export default GlobalState;

Provide Context.jsx

As shown above, this is how the context is passed. However, you can render any components within the 
<ShopContext.Provider></ShopContext.Provider>

Whatever child component is wrapped inside the ShopContext will be able to access the context object.

Now that we have supplied the values, it’s time to access them with the consumer.

Context Consumer

There are two ways in which the data can be accessed:

  1. Using Context.Consumer
  2. Using static contextType

Using Context.Consumer:

import React, { Component } from 'react';
import ShopContext from '../context/shop-context';
import Navigation from '../components/navigation';
import './product.css';

class ProductsPage extends Component {
  render() {
    return (
      <ShopContext.Consumer>
        {context => (
          <React.Fragment>
            <Navigation
              cartItemNumber={context.cart.reduce((count, curItem) => {
                return count + curItem.quantity;
              }, 0)}
            />
            <main className="products">
              <ul>
                {context.products.map(product => (
                  <li key={product.id}>
                    <div>
                      <strong>{product.title}</strong> - ${product.price}
                    </div>
                    <div>
                      <button
                        Add to Cart
                      </button>
                    </div>
                  </li>
                ))}
              </ul>
            </main>
          </React.Fragment>
        )}
      </ShopContext.Consumer>
    );
  }
}

export default ProductsPage;

Consume Context.jsx

In the above example, the context is nothing but the values of the context object. All the values which we defined during creating the context object are easily accessed here. Yeah, it’s that easy!

Using static contextType

import ShopContext from '../context/shop-context'

class CartPage extends Component {
  static contextType = ShopContext

  componentDidMount() {
    // Some advantage of static contextType: We can now also access Context in the rest of the component
    console.log(this.context)
  }

  render() {
    return (
      <React.Fragment>
        <Navigation
          cartItemNumber={this.context.cart.reduce((count, curItem) => {
            return count + curItem.quantity
          }, 0)}
        />
        <main className="cart">...</main>
      </React.Fragment>
    )
  }
}

export default CartPage

static contextType.jsx

This is a simple way of how context can be accessed with contextType. Using this.context you can access the entire context object similar to that of context Consumer.

So far what we have seen in the example is about passing the data and accessing it. Let’s look into how to update the context Object.

Updating Context Object

Updating context state is pretty easy, like how we update the values in the state normally. We have to pass a method from the component which will be used to update the values in that state of context. Once the values are updated via setState, it will automatically render the new values in all the components where the context object is being consumed. No rocket science 🚀 here!

// Method to addProductToCart
<div>
  <button
    onClick={context.addProductToCart.bind(this, product)}
  >
    Add to Cart
  </button>
</div>

// Method to removeProductFromCart
<div>
  <button
    onClick={this.context.removeProductFromCart.bind(this,cartItem.id)}
  >
    Remove from Cart
  </button>
</div>

Add update Method.jsx

The above two methods are how we usually pass a function from the child component to parent component while working with React.

import React, { Component } from 'react';

import ShopContext from './shop-context';

class GlobalState extends Component {
  state = {
    products: [
      { id: 'p1', title: 'React 16 Sticker + T-shirt', price: 29.99 },
      { id: 'p2', title: 'Vue.js T-shirt', price: 9.99 },
      { id: 'p3', title: 'Angular T-shirt', price: 8.99 },
      { id: 'p4', title: 'JS Notebook', price: 2.99 }
    ],
    cart: []
  };

  addProductToCart = product => {
    console.log('Adding product', product);
    const updatedCart = [...this.state.cart];
    const updatedItemIndex = updatedCart.findIndex(
      item => item.id === product.id
    );

    if (updatedItemIndex < 0) {
      updatedCart.push({ ...product, quantity: 1 });
    } else {
      const updatedItem = {
        ...updatedCart[updatedItemIndex]
      };
      updatedItem.quantity++;
      updatedCart[updatedItemIndex] = updatedItem;
    }
    this.setState({ cart: updatedCart });
  };

  removeProductFromCart = productId => {
    console.log('Removing product with id: ' + productId);
    const updatedCart = [...this.state.cart];
    const updatedItemIndex = updatedCart.findIndex(
      item => item.id === productId
    );

    const updatedItem = {
      ...updatedCart[updatedItemIndex]
    };
    updatedItem.quantity--;
    if (updatedItem.quantity <= 0) {
      updatedCart.splice(updatedItemIndex, 1);
    } else {
      updatedCart[updatedItemIndex] = updatedItem;
    }
    this.setState({ cart: updatedCart });
  };

  render() {
    return (
      <ShopContext.Provider
        value={{
          products: this.state.products,
          cart: this.state.cart,
          addProductToCart: this.addProductToCart,
          removeProductFromCart: this.removeProductFromCart
        }}
      >
        {this.props.children}
      </ShopContext.Provider>
    );
  }
}

export default GlobalState;

Add_Update Context.jsx

In the above code snippet, addProductToCart and removeProductFromCart are the two methods that will be used to add/update values inside the context.

But will Context replace Redux 🤔?

  • Context is a clear win when you need to update the values rarely, like storing user objects upon login, language preference, theme colors and so on (as these do not trigger the updates more frequently)
  • Context seems to be simple compared to redux, but you shouldn’t use it where the values update more frequently. It is not optimized for that.
  • Adding Redux to your project brings more dependency packages with them, increasing the bundle size, while context can be used out of the box with React.

Thus from the above points, Context can definitely be used for low-frequency updates but is not recommended for general state management.

I hope in the coming updates that context becomes more powerful and is ready to fully use for state management 💪 🏋.

Happy Learning!

Recommended Courses:

React Native for Mobile Developers

React Native for Mobile Developers

Modern React with Redux [2019 Update]

React Native: Advanced Concepts

React Native: Build Your Own Mobile Apps

React Redux React-Router: From Beginner to Paid Professional