Building a React Shopping Cart with Context API

shopping-cart-react-with-context-api

React has grown to be an industry standard for building interactive user interfaces, it simplifies how complex applications are developed. One of the challenges developers encouter when building applications is how data is being managed across different components. This is where React Context Api comes in. Context API provides a way to pass data through the component trees without having to pass props manually at every level, with the help of Context API we can effectively manage the state of a shopping cart across all components without passing props down the component tree.

In this article we will use the Context API to build a robust shopping cart in a React Application, we will be building the various components, setting up context API, and implementing a payment gateway feature to create a seamless shopping experience for users.



Here is the link to the Live Demo  softmarts.vercel.app

Understanding Context Api

Context API can be compared to a glue that help connects all components in a React Application, It allows components to pass data through every level of the component tree. We can achieve this by creating a Context at the parent component and pass it down to every child component that needs the access to the cart data.
In the next section we will take a closer look at how to create a React application and set up Context API to manage the state of our shopping cart. 

Prerequisite 

To successfully follow this tutorial on building a React shopping cart with the Context API and Integrating Stripe Payment Gateway, it is essential to have a solid understanding of the following:

1. Familiarity with React concepts like components, state management, props, and lifecycle methods is necessary.

2. Knowledge of modern JavaScript features such as arrow functions, template literals, destructuring, and spread/rest operators is crucial for writing React code effectively.

3. Ensure that Node.js and npm (Node Package Manager) are installed on your machine to set up and run the React project locally.

Getting Started: Creating a React App and Installing other dependencies

To begin our tutorial, we will set up a new React project using vite, follow these steps to create your React Project 
  1. Navigate to your project folder, open your terminal
  2.  Run the following command to create a new project using vite npm create @ latest vite
  3. You will be prompted to name your project, give it any name of your choice, I will name mine softmart.
  4. After naming your project, you will be prompted to select a variant. Choose "Javascript" and press enter to proceed.
  5. Navigate to your newly created project directory by running cd "name of your project"
  6. Launch your application in development mode by running npm run dev from your terminal, This will start the development server, then navigate to the url provided( http://localhost:5173) on your browser.

Installing Tailwind Css

We will be styling our project using tailwind css which helps us in styling our project using inline code, follow the following steps to install tailwind css
  1. Run this command on your terminal to install tailwindcss and create a tailwind.config.js file
  2. Open the tailwind.config.js file in your project directory and add the paths to all your template files .
  3. Next, navigate to your index.css file, clear all the existing code, Then input the following code. This code imports the base styles, component classes, and utility classes from Tailwind CSS.

Building Our App Component

Now that we have our react project set up and running, lets start building our various components that will make up our shopping cart application. we will create several components to represent different parts of the user interface which includes the Navbar, ProductPage and CartPage components. 

Setting up component structure

To begin, let's organize our components by creating a new folder named components within the src directory. This folder will house all the components we'll be working with throughout our application.Navigate to your src folder and create a new folder called components.

The Navbar Component

Within your component folder create a new file named Navbar.jsx , this component will be responsible for having our logo and cart information, as well as providing navigation links. 

The ProductPage Component

To create the productpage component, In your component folder create a new file named Product.jsx, this component will be responsible for displaying all the products in our shopping cart, we will be fetching our cart product from the fakestoreapi, copy the following code.

In the code above we initialize a state variable called products using the useState hook. This state will hold an array of product data that will be fetched from the API. The fetchProducts function is an asynchronous function responsible for fetching product data from the API. We use the fetch API to make a GET request to the fakestoreAPI endpoint (https://fakestoreapi.com/products/). Upon receiving the response, we parse the JSON data and update the products state with the fetched product data using setProducts. We then use the useEffect hook with an empty dependency array to ensure that fetchProducts is called only once when the component mounts. We  render each product dynamically by mapping the product array. For each product, we display its image, title, sliced description (limited to 80 characters), and price. An "Add to Cart" button is provided to allow users to add the product to their cart. The ProductPage component effectively fetches and displays product data from the API.

The CartPage

To create the cart page component. In your component folder create a new file named cartPage.jsx, this component will be responsible for displaying the items we have added to cart, the total sum of all the items and a checkout feature. Lets set our component up, copy the code below.

Navigating between Pages

We have successfully set up our different pages and will need to navigate from one page to another, in order to achieve a seamless navigation between pages we will use the React Router, a tool for page navigation in React applications. Let's begin by installing React Router using the following command:
After installing React Router Dom, navigate to your App.jsx and update it with the code below:
In the code above, we've imported BrowserRouter as Router, Routes, and Route from react-router-dom. The BrowserRouter component is used to provide navigation capabilities to our application, while the Routes component serves as a container for defining different routes. Each Route component specifies a path and the corresponding component to render when that path is matched. In this case, the '/' path corresponds to the Products component, and the '/cart' path corresponds to the CartPage component.
Update your Navbar component to enable page navigation using the Link feature from react-router-dom. Here's the modified code:

Setting up the cart context

To establish the foundation for sharing cart data across our application, let's leverage the Context API in React. Follow these steps to set up the cart context:

Create Context File:

  • Navigate to the src directory of your project.
  • Inside the src directory, create a new folder named context.
  • Within the context folder, create a new file named cart.jsx. This file will serve as the container for our cart context.

Define Cart Context:

  • Open the cart.jsx file.
  • Begin by importing createContext from React.
  • Create a new context object called CartContext using createContext().

Here's how you can implement this in code:

By setting up the cart context using the Context API, we establish a centralized store for cart-related data that can be accessed by any component within our application. 

Wraping the component: 
  • After creating the context object, wrap the component that needs access to the shared data with a provider component.
  • The provider component accepts a value prop that holds the shared data.
  • Any component that is a child of the Provider component can access that shared data.
Here is an example of how you can wrap components with the provider:

We want our cart to be able to get the total cart items available, add items to the cart , remove items from the cart and clear the cart items, we will be building each of this feature.

Adding Item to the Cart: In order to add item to the cart lets create a function for this feature, copy the code below:

In the code above: 
  • We utilize the find method to determine whether the item being added already exists in the cart. 
  • If it does, the existingItem variable holds a reference to that item. 
  • Then we check if the item exists in the cart, we increment its quantity by mapping over the cartItems array and adjusting the quantity of the matching item.
  • If the item is not yet in the cart, we append it to the cart with a quantity of 1 by spreading the existing cartItems array and appending the new item with an initial quantity of 1.
  • Finally, we update the state of the cart items using the setCartItems function, passing in the updated array of cart items.

Removing Item from the Cart: Lets create a function to implent the remove from cart feature, copy the code below:

In the code above: 

  • We utilize the find method to determine whether the item being removed exists in the cart. If it does, the existingItem variable holds a reference to that item.
  • We also check if the existing item's quantity is equal to 1. If the quantity is indeed 1, it means that removing one more instance of the item will result in an empty cart for that item.
  • If the item exists in the cart and its quantity is 1, we remove it from the cart by filtering out the item with a quantity of 1 from the cartItems array.
  • If the item exists in the cart and its quantity is greater than 1, we decrement its quantity by mapping over the cartItems array and reducing the quantity of the matching item by 1.
Get Cart Total : Lets move on to create a function that will be utilized for calculating the total sum of items price in our cart, copy the code below:  

In the code above: 
  • The reduce method is used to iterate over all items in the cartItems array and accumulate a total value.
  • Within the reduce method, we provide a callback function with two parameters: total and item. The total parameter represents the accumulated total value, while the item parameter represents each item in the cartItems array.
  • In each iteration, we calculate the subtotal for the current item by multiplying its price (item.price) by its quantity (item.quantity). This gives us the total price for all instances of the item in the cart.
  •   The reduce method accepts an initial value as its second argument. In this case, we set the initial value to 0, indicating that the accumulation starts from zero.
  • The reduce method returns the final accumulated total value, which represents the total cost of all items in the shopping cart, taking into account their respective quantities.

Clear Cart: Let's move on to creating a function that will be used for clearing every item in our cart, copy the code below: 

In the code above:

  • The clearCart function clears the contents of the shopping cart by setting the cartItems state to an empty array.
To ensure the persistence of our cart data even after closing the browser, we'll utilize the `useEffect` hook to update the `localStorage` whenever there's a change in the `cartItems` state. While in a production environment, a database would typically be employed for this purpose. Include this code:

In the code above:
  • This `useEffect` hook is responsible for updating the `localStorage` whenever the `cartItems` state changes.
  • The second argument of the `useEffect` hook is an array `[cartItems]`, which specifies that the effect should be triggered whenever the `cartItems` state changes.
  • Within the effect function, `localStorage.setItem()` is called to update the `cartItems` data in the browser's local storage. The `setItem()` method takes two arguments: a key `"cartItems"` and a value `JSON.stringify(cartItems)`.
  • Before storing the `cartItems` data in the local storage, it is converted to a JSON string using `JSON.stringify()`. This is necessary because `localStorage` can only store string data.
  • Since the effect depends on the `cartItems` state, it will be executed whenever `cartItems` changes, ensuring that the local storage is always synchronized with the latest cart data.

To ensure that the cart's contents are preserved during browser refreshes, we'll configure our application to initialize the cartItems state by retrieving data from local storage. If the data isn't available, we'll set cartItems to an empty array. 

In the code above:

  •  The useState hook is used to initialize the cartItems state variable. It is declared with an initial value obtained from localStorage or an empty array if no cart items are found in localStorage.
  • The localStorage.getItem("cartItems") method retrieves the value associated with the key "cartItems" from the browser's localStorage.
  • The ternary operator ? is used to check if a value exists for "cartItems" in localStorage. If a value is found, it is parsed from a JSON string to a JavaScript object using JSON.parse(localStorage.getItem("cartItems")). This parsed value becomes the initial state of cartItems.
  •  If no value is found for "cartItems" in localStorage, the expression evaluates to null, and the ternary operator sets the initial state of cartItems to an empty array [].

Consuming the Cart Context

We have successfully created the cart context within our aplication. The next step is to make it available across all components requiring it. This involves wrapping the App component with the CartProvider component. To implement this, navigate to App.jsx and update the code snippet.

Consuming in the Product Page

Its time to spice our produc page, by making it more interactive, in this session you will see howw to consume the the functions from the cartContext, navigate to the productPage.jsx and include the following code:

In the code above:
  • The useContext hook is imported from the React library. It allows functional components to consume context values provided by a ContextProvider.
  • The CartContext is imported from  (../Context/Cart). This context contains the necessary data and functions related to the shopping cart.
  • Within the Products component, the useContext hook is used to consume the CartContext. This allows the component to access the cartItemsaddToCart, and removeFromCart values provided by the context.
Next, we will make the Add to Cart button functional , lets create an onClick attribute, update the code with this  
The `onClick` event triggers the `addToCart` function with the `product` parameter when the button is clicked, allowing users to add items to their cart.

To enhance user experience, we'll modify the button behavior to toggle between incrementing and decrementing when clicked. To achieve this, we'll introduce a function called `isCart` to determine if a specific product is already in the cart.
In the code above the `isCart` function checks whether a given `product` is already present in the `cartItems` array by searching for a matching `id`, returning `true` if found, and `false` otherwise.

Now lets use the isCart function to determine if a product have been clicked or not, update your code as below.  
In the code above: 
  • The expression {!isCart(product) ? (...) : (...)} performs a conditional check. If isCart(product) returns false (indicating the product is not in the cart), the first block of JSX elements is rendered. Otherwise, if isCart(product) returns true (indicating the product is already in the cart), the second block of JSX elements is rendered.
  • In the first block, when the product is not in the cart, a button with the text "Add to Cart" is rendered. Clicking this button triggers the addToCart function to add the product to the cart.
  • In the second block, when the product is already in the cart, three elements are rendered: an "Add" button, the quantity of the product in the cart, and a "Minus" button. Clicking the "Add" button increments the quantity of the product in the cart, while clicking the "Minus" button decrements the quantity.
  • The onClick handlers attached to the buttons call the addToCart and removeFromCart functions.
The product page code should look like this.  

Consuming in the NavPage

With the product page completed, our next step is to integrate the cart context into the NavPage. This involves providing the `NavPage` component with access to the `cartItems` array from the cart context, enabling us to display the number of items in the cart. To accomplish this, navigate to `NavPage.jsx`, import the `CartContext` as done for the product page, and also import `useContext` from React. Within the `Navbar` component, utilize the `useContext` hook to consume the `cartItems` from the `CartContext`. Your code should resemble the following.  

Consuming in the CartPage

The next step is to get our context data from the cart context and consume them in our cart Page. To leverage our cart context and integrate its data into our cart page, we utilize the useContext hook to access the cartItems array, along with functions like addToCart, removeFromCart, and getCartTotal. This allows us to display the cart summary, including subtotal, individual item details, and provide functionality for adding or removing items from the cart. Additionally, a checkout button is included to proceed with the purchase. Copy the code below:  
In the code above: 
  • The useContext hook is utilized to access the CartContext, allowing the component to retrieve essential data and functions related to the shopping cart.
  • The component begins by rendering the overall summary of the cart, including a heading for "Cart Summary" and displaying the subtotal of all items in the cart.
  • It iterates over the cartItems array retrieved from the context and renders individual items within the cart. Each item is displayed with its image, title, price, and quantity. Users can see a preview of the item and its associated details.
  • For each item, buttons are provided to adjust its quantity in the cart. Users can increment or decrement the quantity of an item using the "Add" and "Minus" buttons respectively. These actions are facilitated by the addToCart and removeFromCart functions obtained from the context.
  • Finally, a checkout button is displayed at the bottom of the page, allowing users to proceed with their purchase. This button doesn't have functionality implemented within the provided code snippet, but it typically would trigger actions to complete the checkout process, such as sending the cart data to a backend server for processing.

 Integrating Stripe payment gateway

To elevate the functionality of our project, we'll now integrate the Stripe payment gateway, enabling users to make secure online payments for their items.

  1. Setting Up Stripe Account: Begin by navigating to the Stripe website in your browser. Click on the "Sign In" button to access the platform. If you don't have an account yet, click on "Sign Up" and follow the prompts to create your Stripe account. After successfully creating your account, you'll be redirected to a page where you'll find your secret key. Be sure to copy this key as we'll need it later in the process.
  2. Backend Setup: Outside your React frontend project, create a new folder named [your-project]-be to house the backend code responsible for communicating with Stripe. Within this folder, create a new file named Server.js and paste your secret key there, ensuring to comment it out for security purposes.
  3. Creating Backend: Now, let's create a full backend to handle Stripe requests. Navigate to your backend folder in the terminal and run npm init --yes to generate a package.json file. Next, install the necessary dependencies by running npm install express cors stripe. These packages will provide the essential functionality for setting up our backend.

Building the Backend (Server.js)

The next step is to write the server.js backend which is responsible for communicating with the backend, navigate to your server,js and copy the following code:
  1. Import Dependencies: We import the express framework for creating our server, cors middleware for handling cross-origin resource sharing, and stripe library for interacting with Stripe's API. The stripe library is initialized with your secret API key.
  2.  Set up Express App: We create an Express application, enable CORS, set up static file serving from a public directory, and parse JSON requests. 
  3. Define Checkout Endpoint: We define a POST endpoint /checkout to handle checkout requests. This endpoint will receive data about the items to be purchased.
  4. Handle Checkout Request:We extract the items array from the request body, which contains the items to be purchased.
  5. Create Line Items for Checkout Session: We map over each item in the cartItems array to create line items required for the Stripe checkout session. Each line item includes the product's name, price in cents, and quantity.
  6. Create Stripe Checkout Session: We use the stripe.checkout.sessions.create method to create a new checkout session with specified parameters, including payment method types, line items, mode, and success/cancel URLs.
  7. Return Checkout Session URL: Finally, we send back a JSON response containing the URL of the created checkout session to redirect the user to Stripe's payment page.
  8. Start Server: Finally, we start the Express server, listening on port 4000. This allows the server to receive incoming requests and handle them accordingly, including the checkout requests to initiate payment via Stripe.
  9. Run Backend Server: To start the backend server and make it operational, navigate to the backend directory in your terminal and execute the command npm start.
Your sever.js file should look like this:

Submiting the user cart to the backend

To complete the payment process, we need to transmit the user's cart items from the CartPage component to the Stripe backend. To accomplish this, navigate to your CartPage.jsx file. Within this file, create a new function Checkout responsible for sending the user's cart items to Stripe. Below is the code snippet for this function:
This function utilizes the Fetch API to make a POST request to the specified endpoint on the backend server, which in this case is http://localhost:4000/checkout. It sends the cart items as JSON data in the request body. Upon receiving a response from the server, it parses the response as JSON. If the response contains a URL, it redirects the user to that URL using window.location.assign(). This URL typically leads to the Stripe payment page.
Then, modify the checkout button to initiate the redirection to Stripe upon being clicked: 

Finally we will nedd to create a company account in our stripe, navigate to Account details, and input your company name and save it.

Hurray you have successfully created your project , now lets see it in action by running it:
  • Navigate to your Frontend project and run npm run dev, to start your frontend application
  • Navigate to your backend project and run npm start to start your backend application

Here is the link to the GitHub repository of the project Timmydee/Softmarts

Here is the link to the Live Demo  softmarts.vercel.app

Conclusion

Throughout this tutorial, we explored the fundamentals of React components, Context API, React Router Dom and integrating a payment gateway with Stripe. With these skills, you can embark on creating robust e-commerce solutions that prioritize user experience and efficiency. As you continue your coding journey, remember to explore beyond the basics and experiment with advanced features. 

No comments:

Powered by Blogger.