In this lesson, we will display a cart page when users click the cart icon in the Header. It will display the details of the products users have added to their cart. Along with listing items in the cart, we will also display total price details as shown:
Let's get started by creating a Cart
directory inside the src/components
directory. We will have an index.jsx
file as the entry point for the cart page:
First, let's define the Cart
component by just returning a Header
:
Next, we will add a route for the cart page. To start, let's include an entry for the /cart
path in the routes.js
file:
In the App
component we will add a Route
component for the cart page:
Add a link to the cart page from the cart icon in the header by wrapping it with a Link
component:
Now, clicking the cart icon will take you to the cart page:
The next step is to display the details of cart items. We already have the slugs and quantity of each product added to the cart in the Zustand store. We will need to retrieve more details about each product, such as name and price.
To achieve this, we can get the slugs from the cart items store and then send a request to the product details API to fetch the details of each product using these slugs. We can use the Promise.all method to make multiple parallel requests to the product details API for the product slugs in the cart.
The productsApi.show
method returns a Promise. We have mapped the slugs array to create an array of promises, each corresponding to the request for the product details of a slug. These promises are then passed to the Promise.all
method. The Promise.all
method itself returns a promise. If all the requests are successful, this promise returns an array of responses for the requests. It rejects if any of the input promises reject, providing the first rejection reason. In this context, the responses will form an array of products corresponding to the slugs.
Now that we have the product details, let's create a state to store this details. We will also create a state to handle the loading state.
We will add early returns in our Cart
component to display a page loader when the product data is being fetched and to show a no data page when there are no items in the cart:
Now, let's create a ProductCard
component to define the JSX for displaying the product details. It should include the name, image, MRP, offer price, and an option to update the product quantity:
Let's map the products
data over the ProductCard
component from the Cart
component to display the cart items:
Since we are persisting the cart items to local storage, the already added product may no longer be available, or the current number of available stocks may be less than the already selected quantity. Therefore, we should validate these conditions before displaying the cart items to ensure the accuracy of the data.
We will compare the selected quantity of each product in the store with the available quantity from the response. If the available quantity is less than the selected quantity, we will update the selected quantity in the store. For this purpose, we will need both cartItems
and setSelectedQuantity
from the store. We were only extracting the slugs from the cartItems
object. We will replace the previous usage of useCartItemsStore
to access the entire store.
Next, we can iterate over the responses array and set the selected quantity to the available quantity for the products whose stocks have been reduced. If any product in the cart is sold out, we will display a Toastr
to inform the user that the product is no longer available.
Now, when you open the cart page, you'll receive a toastr message if any products added to the cart are sold out:
Adding feature to remove cart item
Next, we will attach a delete button to the product card so that we can directly delete an item without decreasing its count:
First let's add a function to the cart items store to remove an item from the cartItems
object.
We can invoke the above removeCartItem
function when the user clicks the delete button in the product cart:
However, before executing irreversible actions such as deletion or invoking permissions, it's prudent to ask users for confirmation to avoid accidental actions. Therefore, we will show an alert using Alert component from neetoUI
as shown:
We will create a state shouldShowDeleteAlert
to show an alert when the user clicks a delete button. On confirming the deletion using the submit button, we will call the removeCartItem
function:
Confirmation modals should guide the users through decision-making without unnecessary disruption. They should have clear titles, concise bodies, and actions that are predictable and scannable. For detailed guidelines on creating confirmation modals, please refer to this document, which outlines the practices we follow at BigBinary.
We should include the cartItems
in the dependency array of the useEffect
hook to ensure synchronization of the product list with the store:
Consolidating cart information
Let's present consolidated information about the items added to the cart, including total MRP, total discounts, the total price to be paid after applying discounts, and the total number of items on the cart page:
The data for each product we receive from the backend contains the MRP and the offer price. We could calculate the total MRP by taking the sum of the MRP of each product multiplied by the quantity of the product in the cart. Similarly, we can calculate the total offer price.
We will create a utility function named cartTotalOf
in the src/components/utils.js
file for this:
We will define the cartTotalOf
function to calculate the summation of a particular key in product details. For this, we will pass two arguments: the products
array, which should contain the details of products in the cart and priceKey
to indicate the value for which we want to find summation.
For instance, if we pass the following array and "mrp" as the key, the function should return the sum of the MRP for the given array:
As discussed earlier, Zustand provides us APIs to access state outside components and hooks. The Zustand hooks come with two methods, getState
and setState
, to access state outside the components. We will use the getState
method on the useCartItemsStore
hook to access the cart items inside our utility function:
Instead of passing hardcoded strings, we will create constants for the price keys inside src/components/constants.js
and use them inside cart component:
Let's define a component to show these consolidated details. Create a file named PriceCard.jsx
under the Cart
directory. We will pass the totalMrp
and totalOfferPrice
calculated from the Cart
component to the price card component and display the total MRP, discounts, offer price, and items count:
Let's use the PriceCard
component inside the Cart/index.jsx
file:
In the later part of this course, we will introduce a Checkout
page to confirm orders by entering contact information and a shipping address. However, for now, let's add a "Buy now" button to navigate to the "/checkout" path, corresponding to the Checkout
page, in our PriceCard
component:
First, let's add the "/checkout" path to routes.js
file:
Let's include the "Buy now" button in the PriceCard
component, directing it to the "/checkout" path:
At last, let's add the "Buy now" button to the Product
page, allowing users to make a direct purchase from the product details page:
In addition to redirecting to the checkout path, we will add the product to the cart by setting the quantity to 1
:
Let's commit the new changes:
You can verify the changes here.