React_12

React_12

In this blog, we'll learn state management using Redux.

useContext vs Redux

You can use context, if you are building a small application. But, when your application grows, there would be so many context layers that need to be managed. Redux gives you a proper way of managing, modifying and reading that data. A lot of companies use Redux or alternatives for state management like MobX, Vuex, Recoil, Rematch, RxJS, Context, Flux and many more.

Redux has cons as well:

  • It is complex to setup.

  • It has a huge learning curve.

  • It is complicated.

Only use Redux if you have a large scale application with lots of data handling.

Advantage of using Redux Toolkit over Redux

The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:

  • "Configuring a Redux store is too complicated"

  • "I have to add a lot of packages to get Redux to do anything useful"

  • "Redux requires too much boilerplate code"

We can't solve every use case, but in the spirit of create-react-app, we can try to provide some tools that abstract over the setup process and handle the most common use cases, as well as include some useful utilities that will let the user simplify their application code.

Redux store architecture

Let's say that we are building the cart functionality for our Foodhouse app. Whenever a user clicks on the add to cart button, we dispatch an action, that calls a reducer function, which updates a slice of redux store. We read this data from the store using a selector which updates the cart. Thus, the cart component subscribes to the store using the selector.

Install these packages to get started with redux:

npm install @reduxjs/toolkit
npm install react-redux

Implementing Cart functionality

// utils/store.js
import { configureStore } from "@reduxjs/toolkit";
import cartSliceReducer from "./cartSlice";

const store = configureStore({
  reducer: {
    cart: cartSliceReducer,
  },
});

export default store;
// utils/cartSlice.js
import { createSlice } from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: "cart",
  initialState: {},
  reducers: {
    increaseItemQuantity: (state, action) => {
      const id = action.payload.id;
      if (!state[id]) {
        state[id] = {
          item: action.payload,
          quantity: 0,
        };
      }
      state[id].quantity += 1;
    },
    decreaseItemQuantity: (state, action) => {
      const id = action.payload.id;
      if (state[id]) {
        state[id].quantity -= 1;
        if (state[id].quantity === 0) {
          delete state[id];
        }
      }
    },
    removeItem: (state, action) => {
      const id = action.payload.id;
      if (state[id]) {
        delete state[id];
      }
    },
    clearCart: (state) => {
      Object.keys(state).forEach((id) => {
        delete state[id];
      });
    },
  },
});

export const {
  increaseItemQuantity,
  decreaseItemQuantity,
  removeItem,
  clearCart,
} = cartSlice.actions;

export default cartSlice.reducer;
// components/Cart.jsx
import { useSelector, useDispatch } from "react-redux";
import { clearCart } from "../utils/cartSlice";
import { getDictionaryLength } from "../utils/helper";
import FoodItem from "./FoodItem";

const Cart = () => {
  const cartItems = useSelector((store) => store.cart);
  const dispatch = useDispatch();
  const handleClearCart = () => {
    dispatch(clearCart());
  };
  const numberOfItems = getDictionaryLength(cartItems);

  return (
    <div>
      <h1 className="font-bold text-3xl">
        Cart Items - {numberOfItems > 0 ? numberOfItems : "Empty"}
      </h1>
      {numberOfItems > 0 && (
        <button className="p-2 m-5 bg-green-100" onClick={handleClearCart}>
          Clear Cart
        </button>
      )}
      <div className="flex flex-wrap">
        {Object.values(cartItems).map(({ item, quantity }, index) => (
          <FoodItem
            key={`${index} - ${item.id}`}
            {...item}
            quantity={quantity}
          />
        ))}
      </div>
    </div>
  );
};

export default Cart;

// components/FoodItem.jsx
import { useDispatch } from "react-redux";
import {
  increaseItemQuantity,
  decreaseItemQuantity,
  removeItem,
} from "../utils/cartSlice";
import { IMG_CDN_URL } from "../config";

const FoodItem = ({ id, name, description, imageId, price, quantity }) => {
  const dispatch = useDispatch();

  const handleIncreaseQuantity = () => {
    dispatch(increaseItemQuantity({ id: id }));
  };
  const handleDecreaseQuantity = () => {
    dispatch(decreaseItemQuantity({ id: id }));
  };
  const handleRemoveItem = () => {
    dispatch(removeItem({ id: id }));
  };

  return (
    <div className="w-56 p-2 m-2 shadow-lg bg-pink-50">
      <img className="w-full" src={IMG_CDN_URL + imageId} />
      <h2 className="font-bold text-xl">{name}</h2>
      <h3>{description}</h3>
      <h4>Rupees: {price / 100}</h4>
      <h4>Quantity: {quantity}</h4>
      <h5>
        <button
          className="mx-5 bg-green-300 hover:cursor-pointer"
          onClick={handleIncreaseQuantity}
        ></button>
        <button
          className="mx-5 bg-red-400 hover:cursor-pointer"
          onClick={handleDecreaseQuantity}
        ></button>
        <button
          className="mx-5 bg-blue-400 hover:cursor-pointer"
          onClick={handleRemoveItem}
        ></button>
      </h5>
    </div>
  );
};

export default FoodItem;

That's all for now folks. See you in the next blog!