State management in React with useContext and useReducer

State management in React with useContext and useReducer

ยท

7 min read

Table of contents

No heading

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.

productspage.png

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 be cart-context.js. In which we will first create a cartcontext using the createContext 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 the CartProvider which is a react component that contains cartContext.Provider. Using cartContext.Provider we can consume data that we want to pass to other places. I have given totalAmount to the value in cartContext.Provider to pass to other pages. We are passing the children prop. Read more about children here. Here any component that will be passed as a prop this cartContext.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>
  );
};

cartpage.png

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.

cartpage1.png

Conclusion : Overall we did four steps to manage the state, which is described below.

  1. Create context and provider components.
  2. Wrapped component tree inside context Provider, which is provider component.
  3. Added useReducer and defined Reducer function and initialState.
  4. 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..

ย