In this blog post, we will explore how to use the Context API in React to solve the problem of prop drilling. We will discuss what the Context API is and why we need it. Additionally, we will provide a step-by-step guide on creating and using context in four simple steps. To demonstrate the practical implementation of the Context API, we will create a shopping cart project that fetches products from an API and exposes properties such as products, loading status, cart, addToCart function, and removeFromCart function.
Prop drilling refers to the process of passing props through multiple levels of components, even when intermediate components do not require those props. This can lead to code complexity, reduced maintainability, and decreased development efficiency. Prop drilling becomes more problematic as the application grows in size and complexity.
The Context API is a feature provided by React that allows us to share data between components without the need for explicit prop passing. It provides a way to create global state accessible to any component within a specific context. Context API eliminates the need for prop drilling and simplifies the process of sharing data across components.
createContext
method to create a context object that holds the data we want to share. For example, const CartContext = createContext()
.CartContext.Provider
component to wrap the components that need access to the context data. This is where we create different states that we want to expose globally, such as products, loading, cart, etc. We also pass these states as values to the CartContext.Provider
component. For example, <CartContext.Provider value={{products, loading, cart}}> ... </CartContext.Provider>
.useContext
hook to access the context data in any component that is inside the CartContext.Provider
component. We pass the context object as an argument to the hook and get back the values we need. For example, const {products, loading, cart} = useContext(CartContext)
.Using the above steps, create the shopping cart project: where we fetch the products from this endpoint: api/products, we create the content that exposes the following properties: products, loading boolean, cart, addToCart function, removeFromCart function. Add the code snippets for each step.
Step 1: Create the Context
cart-context.js
and import React from ‘react’.CartContext
using React.createContext()
.CartContext
as a default export.// cart-context.js
import {createContext} from 'react';
const CartContext = createContext();
export default CartContext;
Step 2: Create the Provider using the Context.
CartProvider
that will act as our provider component.React.useState
to create three states: products, loading, and cart. Initialize them with empty arrays or false values.React.useEffect
to fetch the products from api/products using fetch
or axios
. Set loading to true before fetching and set loading to false after fetching. Set products to the fetched data.React.useState
or React.useReducer
to manage the cart state.CartContext.Provider
component with all the states and functions as values. Wrap it around {props.children}
so that it can render any child components.export function CartProvider({ children }) {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [cart, setCart] = useState([]);
useEffect(() => {
async function fetchProducts() {
try {
const response = await fetch('/api/products'); // Replace with your API endpoint
const productsData = await response.json();
setProducts(productsData);
setLoading(false);
} catch (error) {
console.error('Error fetching products:', error);
setLoading(false);
}
}
fetchProducts();
}, []);
const addToCart = (product) => {
setCart((prevCart) => [...prevCart, product]);
};
const removeFromCart = (productId) => {
setCart((prevCart) => prevCart.filter((item) => item.id !== productId));
};
const contextValue = {
products,
loading,
cart,
addToCart,
removeFromCart,
};
return (
<CartContext.Provider value={contextValue}>
{children}
</CartContext.Provider>
);
}
- Step 3: Create the useContext hook to help us use the context
- Create a custom hook called `useCart` that will use the `React.useContext` hook to access the context data.
- Return the context data from the hook.
- The code snippet for this step is
import React from 'react';
import CartContext from './cart-context';
const useCart = () => {
// use the useContext hook to access the context data
const context = React.useContext(CartContext);
// return the context data
return context;
};
export default useCart;
In your App.js
(or equivalent entry point), import and wrap your application with the CartProvider
component.
import React from 'react';
import { CartProvider } from './CartContext';
import MainComponent from './MainComponent'; // Update with your component's path
function App() {
return (
<CartProvider>
<MainComponent />
</CartProvider>
);
}
export default App;
Now, in your components, you can use the useCart
hook to access the context properties and functions.
import React from 'react';
import { useCart } from './CartContext';
function MainComponent() {
const { products, loading, cart, addToCart, removeFromCart } = useCart();
if (loading) {
return <div>Loading...</div>;
}
// Function to calculate the subtotal of an item
const calculateSubtotal = (item) => {
return item.price * item.quantity;
};
// Calculate the total of the entire cart
const cartTotal = cart.reduce((total, item) => total + calculateSubtotal(item), 0);
return (
<div>
<h1>Products</h1>
{products.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>{product.description}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
))}
<h1>Cart</h1>
{cart.map((item) => (
<div key={item.id}>
<h3>{item.name}</h3>
<p>Price: ${item.price}</p>
<p>Quantity: {item.quantity}</p>
<p>Subtotal: ${calculateSubtotal(item)}</p>
<button onClick={() => removeFromCart(item.id)}>Remove from Cart</button>
</div>
))}
<h2>Total: ${cartTotal}</h2>
</div>
);
}
export default MainComponent;
1. Creating the Context (CartContext.js
):
import React, { createContext, useContext, useState, useEffect } from 'react';
const CartContext = createContext();
export function useCart() {
return useContext(CartContext);
}
export function CartProvider({ children }) {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [cart, setCart] = useState([]);
useEffect(() => {
async function fetchProducts() {
try {
const response = await fetch('/api/products'); // Replace with your API endpoint
const productsData = await response.json();
setProducts(productsData);
setLoading(false);
} catch (error) {
console.error('Error fetching products:', error);
setLoading(false);
}
}
fetchProducts();
}, []);
const addToCart = (product) => {
setCart((prevCart) => [...prevCart, product]);
};
const removeFromCart = (productId) => {
setCart((prevCart) => prevCart.filter((item) => item.id !== productId));
};
const contextValue = {
products,
loading,
cart,
addToCart,
removeFromCart,
};
return (
<CartContext.Provider value={contextValue}>
{children}
</CartContext.Provider>
);
}
2. Using the Cart Context (MainComponent.js
):
import React from 'react';
import { useCart } from './CartContext';
function MainComponent() {
const { products, loading, cart, addToCart, removeFromCart } = useCart();
if (loading) {
return <div>Loading...</div>;
}
// Function to calculate the subtotal of an item
const calculateSubtotal = (item) => {
return item.price * item.quantity;
};
// Calculate the total of the entire cart
const cartTotal = cart.reduce((total, item) => total + calculateSubtotal(item), 0);
return (
<div>
<h1>Products</h1>
{products.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>{product.description}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
))}
<h1>Cart</h1>
{cart.map((item) => (
<div key={item.id}>
<h3>{item.name}</h3>
<p>Price: ${item.price}</p>
<p>Quantity: {item.quantity}</p>
<p>Subtotal: ${calculateSubtotal(item)}</p>
<button onClick={() => removeFromCart(item.id)}>Remove from Cart</button>
</div>
))}
<h2>Total: ${cartTotal}</h2>
</div>
);
}
export default MainComponent;