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.
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?
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.
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.
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.
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.
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.
There are two ways in which the data can be accessed:
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!
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 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.
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
☞ JavaScript for React Developers | Mosh
☞ Getting Closure on React Hooks
☞ React + TypeScript : Why and How
☞ JavaScript Programming Tutorial Full Course for Beginners