Table of contents
No headings in the article.
Hello everyone, ๐ Today in this blog we will discuss how to do state management in react app with useContext and useReducer. Managing state by passing props to every component can be very hectic and time-consuming. So instead of doing prop drilling, we can manage the state by using these two hooks in our application. React context allows us to pass down and use data in whatever component we need in our React app without using props. This means a function that is defined in one component can be used anywhere in the application using context. Here we will learn to do it with an example, so let's start it.
We are taking the example of an e-commerce website. In which we have products on the product page. and we want to add a particular product to the cart. So let's see how to add the product to the cart and manage the state using useContext
and useReducer
hooks.
This is our App.js
file. Which has a product page component. The products page has a list of products as shown below.
import { React, useState } from "react";
import { ProductsPage } from "./ProductsPage";
import "./styles.css";
export default function App() {
return (
<div className="App">
<ProductsPage />
</div>
);
}
In the ProductsPage
I have used the ProductCard
component to show a product with details.
import { React, useState } from "react";
import { ProductCard } from "./ProductCard";
import { products } from "./products";
import "./styles.css";
export const ProductsPage = () => {
return (
<div className="products-page">
<div className="listofproducts">
{products.map((product) => (
<ProductCard product={product} />
))}
</div>
</div>
);
};
For the list of products, I have used the products
file. Which is given below.
import { v4 as uuid } from "uuid";
export const products = [
{
_id: uuid(),
name: "Eye Look Combo- Mascara + Eyeliner + Kajal",
price: "275",
categoryName: "Makeup",
rate: 4,
discount: "5",
image:
"https://images-static.nykaa.com/media/catalog/product/tr:w-200,h-200,cm-pad_resize/9/a/9a6872bNYNYKBC001512_1.jpg"
},
{
_id: uuid(),
name: "Lakme Eyeconic Kajal - Regal Green",
price: "234",
categoryName: "Makeup",
rate: 3,
discount: "10",
image:
"https://images-static.nykaa.com/media/catalog/product/3/3/33ef0c5NYKAC00000132_0.jpg"
},
{
_id: uuid(),
name: "Liquid Foundation With Clay - 115 Ivory",
price: "507",
categoryName: "Makeup",
rate: 4,
discount: "5",
image:
"https://images-static.nykaa.com/media/catalog/product/6/6/66e3c566902395646860_new_1.jpg"
}
];
So our product page looks like this and by clicking on `Add to cart. We want to add our product to the cart.
Context is an API that is built in to React, so we can create and use context directly by importing React into any React project.
- We will create the folder
contexts
. You can give a name as per your convince. It contains context and reducer. The first file name will becart-context.js
. In which we will first create acartcontext
using thecreateContext
function.
import { React, createContext } from "react";
const cartContext = createContext();
- Now we will create a provider which will wrap around our component tree inside
index.js
. Here we can pass any variable, or function that we want to pass into our other files. As In theCartProvider
which is a react component that containscartContext.Provider
. UsingcartContext.Provider
we can consume data that we want to pass to other places. I have giventotalAmount
to the value incartContext.Provider
to pass to other pages. We are passing thechildren
prop. Read more about children here. Here any component that will be passed as a prop thiscartContext.Provider
can get access to the variable, or function that we passed inside the value.
Don't worry if it's hard to understand you will get it soon.
import { React, createContext } from "react";
const cartContext = createContext();
export const CartProvider = ({ children }) => {
const totalAmount = 250;
return (
<cartContext.Provider value={{ totalAmount }}>
{children}
</cartContext.Provider>
);
};
Till now we created cartContext
and CartProvider
. Now it's time to wrap CartProvider
to our component tree inside index.js
.Our top-level component <App />
is wrapped inside CartProvider
.
Now any component inside App can get access to a variable or function, object, or any value that has passed to CartProvider
.
The tip is that value that you want to pass to another component should be passed inside the value
in your provider.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import { CartProvider } from "./contexts/cart-context";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<CartProvider>
<App />
</CartProvider>
</StrictMode>
);
Now let's check it with an example. I have passed totalAmount
to CartProvider
and I want to show it on the cart page. So let's do it.
I created cartPage
as shown below. Here I used useContext
hook to use the cartContext
that we created. and we got the value total amount by using useContext
hook. and our cart page looks like below.
import { React, useContext } from "react";
import { cartContext } from "./contexts/cart-context";
export const CartPage = () => {
const { totalAmount } = useContext(cartContext);
return (
<div>
<h1>Total amount is {totalAmount}</h1>
</div>
);
};
Now we will use useReducer to add the functionality of add to cart.useReducer is an alternative to useState. It Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. Here I used useReducer which takes initialState and cartReducer
function. and I am passing state and dispatch which got from reducer to value in provider. In the initialState, I added to the cart product list, which will have a list of products in the cart, and by clicking on add to cart I want to add the item to the cart.
import { React, createContext, useContext, useReducer } from "react";
import { cartReducer } from "./cartReducer";
export const cartContext = createContext();
export const CartProvider = ({ children }) => {
const initialState = {
cartProductList: []
};
const [state, dispatch] = useReducer(cartReducer, initialState);
return (
<cartContext.Provider value={{ state, dispatch }}>
{children}
</cartContext.Provider>
);
};
export const useCart = () => useContext(cartContext);
So now I can use state and dispatch and update cartProductList
. On ProductCard I added addToCart
function in onClick. Which will use dispatch and use a reducer to add items to cartProductList. In dispatch, I added type ADD_TO_CART
and product as payload. After that, it will take the user to the cart page.
import { React, useContext } from "react";
import { cartContext } from "./contexts/cart-context";
import { useNavigate } from "react-router-dom";
export const ProductCard = ({ product }) => {
const navigate = useNavigate();
const { dispatch } = useContext(cartContext);
const addToCart = (product) => {
dispatch({
type: "ADD_TO_CART",
payload: product
});
navigate("/cart");
};
return (
<div className="card product-card">
<div className="card-padding">
<img className="product-img" src={product.image} alt="" />
<div className="product-card-title bold-font-weight">{product.name}</div>
<div className="card-mrp">
<span className="gray-color">MRP:</span> โน{product.price}
</div>
<div className="rating small-fontsize">
<i className="fas fa-star"></i>
<i className="fas fa-star"></i>
<i className="fas fa-star"></i>
<i className="far fa-star"></i>
<i className="far fa-star"></i>
</div>
</div>
<div className="product-card-footer pink-color">
<i className="far fa-heart card-footer-icon"></i>
<button className="btn card-btn" onClick={() => addToCart(product)}>
Add to Cart
</button>
</div>
</div>
);
};
In reducer, it will have a state and action. In action, it will check the type of payload and we have given a case that is ADD_TO_CART
. Here in that case reducer will update cartProductList
by adding product to it as given below.
export const cartReducer = (state, { type, payload }) => {
switch (type) {
case "ADD_TO_CART":
return {
...state,
cartProductList: [...state.cartProductList, { ...payload, quantity: 1 }]
};
default:
return { ...state };
}
};
On the cart page, we can get cartProductList
from a state that we passed in context.
const { state } = useContext(cartContext);
const productInCart = state.cartProductList;
Here I have a list of products in the cart.
import { React, useContext } from "react";
import { cartContext } from "./contexts/cart-context";
import { Link } from "react-router-dom";
export const CartPage = () => {
const { state } = useContext(cartContext);
const productInCart = state.cartProductList;
return (
<div>
<Link to="/" className="link-no-style btn cart-btn">
go to product page
</Link>
<div className="cart-page">
<div className="listofproducts">
{productInCart.map((product) => (
<div className="card product-card">
<div className="card-padding">
<img className="product-img" src={product.image} alt="" />
<div className="product-card-title bold-font-weight">
{product.name}
</div>
<div className="card-mrp">
<span class="gray-color">MRP:</span> โน{product.price}
</div>
<div className="rating small-fontsize">
<i className="fas fa-star"></i>
<i className="fas fa-star"></i>
<i className="fas fa-star"></i>
<i className="far fa-star"></i>
<i className="far fa-star"></i>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};
The cart page looks like the below.
Conclusion : Overall we did four steps to manage the state, which is described below.
- Create context and provider components.
- Wrapped component tree inside context Provider, which is provider component.
- Added useReducer and defined Reducer function and initialState.
- Used dispatch to provide functionality.
This is a link to the whole code of the given example. I hope you get the idea to do state management with context and useReducer. See you at the next one! Bye..