Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to build an e-commerce cart in React

Building a reliable e-commerce cart in React requires a solid understanding of state management and component composition. As the creator of CoreUI and with over 25 years of experience in software development, I have architected countless e-commerce systems that scale from simple prototypes to high-traffic production environments.

The most efficient approach involves using the React Context API to manage global cart state, ensuring that product data remains synchronized across your entire application. By leveraging modern hooks and robust UI components, we can create a seamless shopping experience that is both performant and easy to maintain.

Use React Context to manage a global cart state and provide functions for adding, removing, and updating product quantities.

1. Creating the Cart Context and Provider

The foundation of any React shopping cart is a centralized state. We use the Context API to avoid “prop drilling” and make the cart data accessible to any component, whether it is a navigation bar showing the item count or a checkout page.

import React, { createContext, useContext, useState, useEffect } from 'react'

const CartContext = createContext()

export const CartProvider = ({ children }) => {
  const [cartItems, setCartItems] = useState([])

  // Load cart from localStorage on mount
  useEffect(() => {
    const savedCart = localStorage.getItem('coreui-cart')
    if (savedCart) setCartItems(JSON.parse(savedCart))
  }, [])

  // Persist cart changes
  useEffect(() => {
    localStorage.setItem('coreui-cart', JSON.stringify(cartItems))
  }, [cartItems])

  return (
    <CartContext.Provider value={{ cartItems, setCartItems }}>
      {children}
    </CartContext.Provider>
  )
}

export const useCart = () => useContext(CartContext)

This code sets up the CartProvider which wraps your application. It initializes the state with an empty array and uses useEffect to synchronize the cart with the browser’s localStorage. This ensures that the user’s selections are preserved even if they refresh the page or return later.

2. Implementing the Add to Cart Functionality

To add items, we need a function that checks if a product already exists in the cart. If it does, we increment the quantity; otherwise, we append the new product. This logic follows the standard pattern for how to add an item to an array in JavaScript.

import { useCart } from './CartContext'

const ProductCard = ({ product }) => {
  const { cartItems, setCartItems } = useCart()

  const addToCart = () => {
    const existingItem = cartItems.find((item) => item.id === product.id)
    
    if (existingItem) {
      setCartItems(
        cartItems.map((item) =>
          item.id === product.id 
            ? { ...item, quantity: item.quantity + 1 } 
            : item
        )
      )
    } else {
      setCartItems([...cartItems, { ...product, quantity: 1 }])
    }
  }

  return (
    <div className='product-item'>
      <h3>{product.name}</h3>
      <button onClick={addToCart}>Add to Cart</button>
    </div>
  )
}

This approach ensures immutability by creating a new array instead of modifying the existing one. If the item is already present, we use map to update only the specific item’s quantity, keeping the rest of the cart intact.

3. Managing Item Quantities and Removal

A functional cart must allow users to adjust quantities or remove items entirely. For removal, we use the filter method, which is the idiomatic way how to remove a specific item from an array in JavaScript.

const updateQuantity = (id, amount) => {
  setCartItems((prevItems) =>
    prevItems.map((item) => {
      if (item.id === id) {
        const newQuantity = Math.max(1, item.quantity + amount)
        return { ...item, quantity: newQuantity }
      }
      return item
    })
  )
}

const removeFromCart = (id) => {
  // Use filter to exclude the item with the matching ID
  setCartItems(cartItems.filter((item) => item.id !== id))
}

The updateQuantity function uses Math.max(1, ...) to prevent the quantity from dropping below one. The removeFromCart function demonstrates how to filter an array in JavaScript to create a new version of the cart without the unwanted product.

4. Calculating Totals and Formatting Currency

Calculating the total cost involves iterating through the cartItems array and summing the product of price and quantity. To ensure a professional look, we must format the number as currency.

const CartSummary = () => {
  const { cartItems } = useCart()

  const subtotal = cartItems.reduce(
    (acc, item) => acc + item.price * item.quantity, 
    0
  )

  const formatPrice = (amount) => {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD'
    }).format(amount)
  }

  return (
    <div className='cart-summary'>
      <p>Total Items: {cartItems.length}</p>
      <h4>Total: {formatPrice(subtotal)}</h4>
    </div>
  )
}

Using reduce is the most efficient way to calculate a single value from an array of objects. The Intl.NumberFormat API provides a localized and reliable way to display prices to your users.

5. Building the UI with CoreUI Components

To make the cart look professional and responsive, we utilize CoreUI components like CCard, CTable, and CButton. This ensures a consistent design system that is accessible and mobile-friendly.

import { 
  CCard, CCardBody, CTable, CTableHead, 
  CTableRow, CTableHeaderCell, CTableBody, 
  CTableDataCell, CButton 
} from '@coreui/react'

const CartTable = () => {
  const { cartItems, removeFromCart, updateQuantity } = useCart()

  return (
    <CCard>
      <CCardBody>
        <CTable align='middle' responsive>
          <CTableHead>
            <CTableRow>
              <CTableHeaderCell>Product</CTableHeaderCell>
              <CTableHeaderCell>Quantity</CTableHeaderCell>
              <CTableHeaderCell>Price</CTableHeaderCell>
              <CTableHeaderCell>Action</CTableHeaderCell>
            </CTableRow>
          </CTableHead>
          <CTableBody>
            {cartItems.map((item) => (
              <CTableRow key={item.id}>
                <CTableDataCell>{item.name}</CTableDataCell>
                <CTableDataCell>
                  <CButton onClick={() => updateQuantity(item.id, -1)}>-</CButton>
                  <span className='mx-2'>{item.quantity}</span>
                  <CButton onClick={() => updateQuantity(item.id, 1)}>+</CButton>
                </CTableDataCell>
                <CTableDataCell>${item.price * item.quantity}</CTableDataCell>
                <CTableDataCell>
                  <CButton color='danger' onClick={() => removeFromCart(item.id)}>
                    Remove
                  </CButton>
                </CTableDataCell>
              </CTableRow>
            ))}
          </CTableBody>
        </CTable>
      </CCardBody>
    </CCard>
  )
}

Using the CoreUI Table component allows you to quickly build complex data layouts with built-in styling. The CButton components provide interactive elements that follow our design guidelines for a polished user experience.

Best Practice Note:

When building e-commerce carts, always treat your state as immutable to avoid difficult-to-track bugs. In CoreUI components, we prioritize predictable data flow by using functional updates in useState. Additionally, always validate product availability on the server side before finalizing a checkout, as client-side state can be manipulated by users. For production apps, consider using a specialized library like Redux Toolkit if your cart logic becomes significantly more complex, though Context API is perfectly sufficient for most e-commerce use cases.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team