Building a shopping cart using React, Redux toolkit
Tags: react, api, frontend, webdev
Published: 2024-05-26 12:59:55

In this article, we'll walk through the process of creating an simple e-commerce application with the Fake store API using React redux and react toolkit, with a focus on implementing a shopping cart. By the end of this tutorial, you will have a functional application with the following features:

1. A product listing page displaying products available for purchase.
2. The ability to add items to the shopping cart.
3. A shopping cart page where users can view, update quantity, and remove items from their cart.

Let's get started !!


1. Create React project using vite:
```npm
npm create vite@latest shopping-cart -- --template react
cd shopping-cart
```
2. We will create three components: Cart, Navbar, and Shop components.

![Project folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixx1vnwpyki4k11sh108.png)

3.We will use fakestore api to get products for our project. Below is our initial Shop.jsx:
```react
import React, { useEffect, useState } from "react";
import axios from "axios";

const Shop = () => {
  const [products, setProducts] = useState([]);
  const getProducts = async () => {
    await axios
      .get("https://fakestoreapi.com/products")
      .then((res) => setProducts(res.data))
      .catch((err) => console.log(err));
  };
  useEffect(() => {
    getProducts();
  }, []);
  return (
    <section className="shop">
      {products.map((product) => (
        <article className="card" key={product.id}>
          <img src={product.image} alt="" />
          <div className="details-div">
            <div className="title-price">
              <p>{product.title}</p>
              <p>{product.price}</p>
            </div>
            <button>Add to cart</button>
          </div>
        </article>
      ))}
    </section>
  );
};

export default Shop;

```
_We now have the products set up so we'll dive into redux toolkit._

**Redux Toolkit**
Redux toolkit is a library that helps us write redux logic. It offers tools that simplify Redux setup and use, reducing boilerplate and enhancing code maintainability.

**Redux Store**
A redux store is a central repository that stores all the states of an application. Store is made up of slices. 
To create our own store we need to install redux toolkit and react redux:
```npm
npm install @reduxjs/toolkit react-redux
```

Create a store.js file inside redux folder inside src folder.

![Project folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrkb69pxb0odj8yn6xvm.png)

In store.js we will create a redux store (store) using _**configureStore**_ provided by reduxjs and export it.
```js
import { configureStore } from "@reduxjs/toolkit";

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

```
To make the store accessible to our application, we need to wrap our App component inside **_Provider_** that allows a prop called store which we will set to our store. This is done inside our main.jsx (could be index.js if you created app using CRA)
Provider is a component that allows Redux store to be made available to all components.

Here is how our main.jsx looks like:
```react
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { store } from "./redux/store.js";
import { Provider } from "react-redux";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
```
**Redux Slice**
A slice is a part of the app's state that is managed by a specific reducer and contains actions related to the state. 
For our project we'll create productsSlice.js file where we'll use createSlice function to create productsSlice.
createSlice accepts an object with the following parameters:

- name -defines the slice's name.
- initialState -sets up the starting state for the slice when the Redux store is first created.
- reducers - these are functions that handle the logic for updating the state. We will create our first reducer _**addProductToCart**_.
We will also export actions and reducer created by our createSlice.
```react
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  products: [],
  cart: [],
};

const productsSlice = createSlice({
  name: "product",
  initialState,
  reducers: {
    addProductToCart: (state, action) => {
      state.cart.push(action.payload);
    },
  },
});

export const { addProductToCart } = productsSlice.actions;

export default productsSlice.reducer;

```

For our productsSlice to be available to the app, we will import reducer created by createSlice inside Store.js and add it to the reducer object of our store. 

Here's how our store.js will be:
```react
import { configureStore } from "@reduxjs/toolkit";
import productReducer from "./productsSlice";

export const store = configureStore({
  reducer: {
    products: productReducer,
  },
});
```
 
To be able to add a product to cart we will use two react-redux hooks:**_useSelector_** and**_useDispatch_**
useSelector allows components to get(select) part of the state that is required. useDispatch allows components to dispatch actions to the redux store. 
Inside Shop.jsx we will import useDispatch from react-redux, invoke it and store in a variable dispatch. Variable dispatch will then be used to dispatch action _addProductToCart_ when button 'Add to cart' is clicked. 

Here's our Shop.jsx:
```react
import React, { useEffect, useState } from "react";
import axios from "axios";
import { useDispatch } from "react-redux";
import { addProductToCart } from "../redux/productsSlice";

const Shop = () => {
  const [products, setProducts] = useState([]);
  const dispatch = useDispatch();
  const getProducts = async () => {
    await axios
      .get("https://fakestoreapi.com/products")
      .then((res) => setProducts(res.data))
      .catch((err) => console.log(err));
  };
  useEffect(() => {
    getProducts();
  }, []);
  return (
    <section className="shop">
      {products.map((product) => (
        <article className="card" key={product.id}>
          <img src={product.image} alt="" />
          <div className="details-div">
            <div className="title-price">
              <p>{product.title}</p>
              <p>{product.price}</p>
            </div>
            <button
              onClick={() =>
                dispatch(
                  addProductToCart({
                    id: product.id,
                    title: product.title,
                    price: product.price,
                    image: product.image,
                  })
                )
              }
            >
              Add to cart
            </button>
          </div>
        </article>
      ))}
    </section>
  );
};

export default Shop;

```
Now that the action is dispatched, the products are successfully added to cart which is part of the state. To display the added products, we will use _useSelector_ inside Cart.jsx to get/extract/select cart which is part of our productsSlice.

Here's our Cart.jsx:
```react
import React from "react";
import { useSelector } from "react-redux";

const Cart = () => {
  const cart = useSelector((state) => state.products.cart);
  console.log(cart);
  return (
    <section className="cart-component">
      {cart.map((product) => (
        <article className="cart-card" key={product.id}>
          <div>
            <img src={product.image} alt="" />
            <button>REMOVE</button>
          </div>
          <div>
            <p>{product.title}</p>
            <div className="button-in-cart">
              <button>-</button>
              <span>{product.quantity}</span>
              <button>+</button>
            </div>
            <p>$:{product.price}</p>
          </div>
        </article>
      ))}
      ;
    </section>
  );
};

export default Cart;

```
To display the count of products in our navbar, we will also get cart using useSelector inside Navbar.jsx. Cart being an array, we will use its length to get the count of products added to cart.

Here's our Navbar.jsx:
```react
import React from "react";
import { useSelector } from "react-redux";

const Cart = () => {
  const cart = useSelector((state) => state.products.cart);
  console.log(cart);
  return (
    <section className="cart-component">
      {cart.map((product) => (
        <article className="cart-card" key={product.id}>
          <div>
            <img src={product.image} alt="" />
            <button>REMOVE</button>
          </div>
          <div>
            <p>{product.title}</p>
            <div className="button-in-cart">
              <button>-</button>
              <span>{product.quantity}</span>
              <button>+</button>
            </div>
            <p>$:{product.price}</p>
          </div>
        </article>
      ))}
      ;
    </section>
  );
};

export default Cart;

```

Using redux we have been able to create a store (source of truth 😅) allowing different components to access the state. In the next part we will ensure that when a similar product is clicked a number of times , it's not added to cart as multiple cart products by adding a quantity property. We will also add other actions such as increase quantity of product, decrease quantity of product, remove product and clear cart.

Source code:
{% github https://github.com/ClarenceG01/shopping-cart-redux %}