Skip to main content

Build a React Shopping Cart with MUI & Tailwind CSS (2025 Guide)



In the competitive landscape of e-commerce, a fast, responsive, and visually appealing shopping cart is crucial. For developers, building such an interface efficiently is key. React provides the foundation for dynamic user interfaces, but its true potential is realized when paired with powerful component and styling libraries.

This comprehensive tutorial will walk you through building a modern React shopping cart application from the ground up. We will harness the power of **Material-UI (MUI)** for its rich set of ready-to-use components and **Tailwind CSS** for its incredible utility-first styling workflow. To create a truly modern and performant app, we will extensively use **React Hooks** to manage state, side effects, and memoized computations.

By following this guide, you'll gain a practical understanding of how to architect a core e-commerce feature, resulting in a scalable and professional looking React application that excels in both performance and user experience.

Why MUI and Tailwind CSS for E-commerce?

-   MUI: Offers a vast library of accessible, pre-designed components like Cards, Buttons, Badges, and Grids. This drastically reduces development time and ensures a consistent, high-quality design system for your e-commerce site.

Tailwind CSS: Its utility-first approach allows for rapid prototyping and customization directly within your JSX. You can fine-tune the look and feel of MUI components or build custom elements without writing a single line of custom CSS.

This combination provides the structure of MUI with the granular control of Tailwind, creating the perfect stack for building beautiful and functional online stores.

Prerequisites

Before you start, make sure you have the following installed:

*   Node.js (v18 or later) and npm

*   A solid grasp of HTML, CSS, and JavaScript (ES6+)

*   Basic knowledge of React concepts (components, props, state)

Step 1: Initialize Your React Project with Vite

Vite offers a blazing-fast development environment, perfect for a modern React project.


1.  Create the React App:

    npm create vite@latest mui-tailwind-cart -- --template react

    

2.  Navigate into your project directory and install dependencies:


    cd mui-tailwind-cart

    npm install


Step 2: Install and Configure Tailwind CSS

Now, let's integrate Tailwind CSS into our project.


1.  Install Tailwind and its dependencies:

    npm install -D tailwindcss postcss autoprefixer

2.  Generate Tailwind configuration files:

        npx tailwindcss init -p

    This creates `tailwind.config.js` and `postcss.config.js`.


3.  Configure Tailwind's template paths:

    Open `tailwind.config.js` and update the `content` array to ensure Tailwind scans all your component files for utility classes.


    // filepath: tailwind.config.js

    /** @type {import('tailwindcss').Config} */

    export default {

      content: [

        "./index.html",

        "./src/**/*.{js,ts,jsx,tsx}",

      ],

      theme: {

        extend: {},

      },

      plugins: [],

    }


4.  Add Tailwind directives to your CSS:

    Replace the contents of `src/index.css` with the following:


    /* filepath: src/index.css */

    @tailwind base;

    @tailwind components;

    @tailwind utilities;

    

Step 3: Install and Integrate MUI

Let's add the Material-UI library and its required icon package.


1.  Install MUI packages:

    npm install @mui/material @emotion/react @emotion/styled @mui/icons-material


2.  Run the development server:

    Start your app to confirm that the setup is correct.

    npm run dev

    Navigate to `http://localhost:5173` in your browser. You should see the default Vite page, but with Tailwind's base styling applied.


Step 4: Building the Shopping Cart Components

Now for the exciting part. We'll structure our app with a product listing and a cart summary.

First, replace the contents of `src/App.jsx` with the code below. This will set up our main component, which manages all the shopping cart logic.


import { useState, useEffect, useCallback, useMemo } from 'react';

import { Container, Typography, Button, Grid, Card, CardContent, CardActions, Paper, Box, List, ListItem, ListItemText, IconButton, Badge } from '@mui/material';

import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';

import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';

import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';


// Sample product data

const products = [

  { id: 1, name: 'React T-Shirt', price: 25.99 },

  { id: 2, name: 'MUI Mug', price: 15.50 },

  { id: 3, name: 'Tailwind Cap', price: 22.00 },

  { id: 4, name: 'Vite Sticker Pack', price: 8.99 },

];


function App() {

  // useState hook to manage the items in the cart

  const [cartItems, setCartItems] = useState([]);


  // useEffect to load cart from localStorage on initial render

  useEffect(() => {

    const storedCart = localStorage.getItem('shoppingCart');

    if (storedCart) {

      setCartItems(JSON.parse(storedCart));

    }

  }, []); // Empty dependency array ensures this runs only once


  // useEffect to save cart to localStorage whenever it changes

  useEffect(() => {

    localStorage.setItem('shoppingCart', JSON.stringify(cartItems));

  }, [cartItems]); // Runs whenever cartItems state changes


  // useCallback to memoize the addToCart function for performance

  const addToCart = useCallback((product) => {

    setCartItems((prevItems) => {

      const itemInCart = prevItems.find((item) => item.id === product.id);

      if (itemInCart) {

        // If item exists, increase quantity

        return prevItems.map((item) =>

          item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item

        );

      }

      // If item doesn't exist, add it with quantity 1

      return [...prevItems, { ...product, quantity: 1 }];

    });

  }, []);


  // useCallback to memoize the removeFromCart function

  const removeFromCart = useCallback((productId) => {

    setCartItems((prevItems) => prevItems.filter((item) => item.id !== productId));

  }, []);


  // useMemo to calculate the total price. This only recalculates when cartItems changes.

  const cartTotal = useMemo(() => {

    return cartItems.reduce((total, item) => total + item.price * item.quantity, 0);

  }, [cartItems]);


  // useMemo to calculate the total number of items in the cart

  const totalItemsInCart = useMemo(() => {

    return cartItems.reduce((total, item) => total + item.quantity, 0);

  }, [cartItems]);


  return (

    <Container maxWidth="lg" className="mt-8">

      <Box className="flex justify-between items-center mb-8">

        <Typography variant="h4" component="h1" className="font-bold text-gray-800">

          Modern E-Commerce

        </Typography>

        <IconButton aria-label="cart">

          <Badge badgeContent={totalItemsInCart} color="primary">

            <ShoppingCartIcon fontSize="large" />

          </Badge>

        </IconButton>

      </Box>


      <Grid container spacing={4}>

        {/* Products Section */}

        <Grid item xs={12} md={8}>

          <Grid container spacing={4}>

            {products.map((product) => (

              <Grid item key={product.id} xs={12} sm={6}>

                <Card className="h-full flex flex-col">

                  <CardContent className="flex-grow">

                    <Typography gutterBottom variant="h5" component="div">

                      {product.name}

                    </Typography>

                    <Typography variant="h6" color="text.secondary">

                      ${product.price.toFixed(2)}

                    </Typography>

                  </CardContent>

                  <CardActions>

                    <Button

                      size="small"

                      variant="contained"

                      startIcon={<AddShoppingCartIcon />}

                      onClick={() => addToCart(product)}

                    >

                      Add to Cart

                    </Button>

                  </CardActions>

                </Card>

              </Grid>

            ))}

          </Grid>

        </Grid>


        {/* Cart Section */}

        <Grid item xs={12} md={4}>

          <Paper elevation={3} className="p-4">

            <Typography variant="h5" component="h2" className="mb-4">

              Shopping Cart

            </Typography>

            {cartItems.length === 0 ? (

              <Typography>Your cart is empty.</Typography>

            ) : (

              <List>

                {cartItems.map((item) => (

                  <ListItem

                    key={item.id}

                    secondaryAction={

                      <IconButton edge="end" aria-label="delete" onClick={() => removeFromCart(item.id)}>

                        <RemoveCircleOutlineIcon className="text-red-500" />

                      </IconButton>

                    }

                  >

                    <ListItemText

                      primary={item.name}

                      secondary={`Quantity: ${item.quantity} - $${(item.price * item.quantity).toFixed(2)}`}

                    />

                  </ListItem>

                ))}

                <ListItem className="mt-4">

                  <ListItemText

                    primary={<Typography variant="h6">Total:</Typography>}

                    secondary={<Typography variant="h6">${cartTotal.toFixed(2)}</Typography>}

                  />

                </ListItem>

              </List>

            )}

          </Paper>

        </Grid>

      </Grid>

    </Container>

  );

}

export default App


Dissecting the Hooks and Functionality


Our shopping cart app effectively uses modern React Hooks to create a smooth and efficient user experience:


1.  useState`: `useState([])` is the heart of our cart, holding an array of item objects. Each time an item is added or removed, this state is updated, triggering a re-render to reflect the changes.

2.  useEffect: We use this hook for persistence.

    *   One `useEffect` runs on the initial component mount (thanks to `[]`) to check `localStorage` for a previously saved cart, ensuring users don't lose their items when they refresh the page.

    *   A second `useEffect` runs whenever `cartItems` is modified, saving the latest version to `localStorage`.

3.  This hook is a key performance optimization. By wrapping `addToCart` and `removeFromCart` in `useCallback`, we ensure these functions are not recreated on every render. This is crucial in larger apps where these functions might be passed as props to many child components, preventing unnecessary re-renders.

4.  useMemo`: This hook is perfect for expensive calculations. We use it to compute the `cartTotal` and `totalItemsInCart`. These values are only recalculated when the `cartItems` array changes, not on every single render. This prevents redundant computations and keeps the UI snappy.


Conclusion

Congratulations! You've just built a robust and scalable React shopping cart application using a powerful, modern tech stack. By integrating **MUI** for components and **Tailwind CSS** for styling, you've seen how quickly a professional-grade UI can be assembled. Most importantly, your use of React Hooks—`useState`, `useEffect`, `useCallback`, and `useMemo`—demonstrates a deep understanding of modern state management and performance optimization in React.

This project serves as an excellent foundation. You can now expand on it by implementing features like quantity adjustments in the cart, a multi-page checkout flow, or fetching products from a real API. The skills you've learned here are directly applicable to building complex, real-world e-commerce applications. Happy coding

Comments

Popular posts from this blog

Upload to AWS S3 from Java API

In this post, you will see code samples for how to upload a file to AWS S3 bucket from a Java Spring Boot app. The code you will see here is from one of my open-source repositories on Github, called document-sharing. Problem Let’s say you are building a document sharing app where you allow your users to upload the file to a public cloud solution. Now, let’s say you are building the API for your app with Spring Boot and you are using AWS S3 as your public cloud solution. How would you do that? This blog post contains the code that can help you achieve that. Read more below,  Upload to AWS S3 bucket from Java Spring Boot app - My Day To-Do (mydaytodo.com)

Addressing app review rejections for auto-renewing subscription in-app purchase (iOS)

The ability to know what the weather is like while planning your day is a feature of  My Day To-Do  Pro and as of the last update it’s also a part of the  Lite version . Unlike the Pro version it’s an auto-renewing subscription based  in-app purchase (IAP)  in the Lite version. What means is that when a user purchases it, the user only pays for the subscription duration after which the user will be automatically charged for the next period. Adding an  auto-renewing  subscription based IAP proved to be somewhat challenging in terms of the app store review i.e. the app update was rejected by the App Review team thrice because of missing information about the IAP. Therefore in this post I will share my experiences and knowledge of adding auto-renewing IAP in hopes to save someone else the time that I had to spend on this problem. In-App purchase This year I started adding IAPs to My Day To-Do Lite which lead to learning about different types of IAP...

Getting started with iOS programming using Swift (Part 1)

I have not been too fond of Objective-C, which was the primary reason for me to stay away from making iOS apps till now. So what changed? Well Apple has done something very interesting recently and that is the introduction of a new programming language i.e. Swift. Swift is awesome, it almost feels like Python, C++ and Objective-C had a baby with some of their good parts in them. So I have been getting to know Swift and it is an awesome language to program in. What I am going to share with this and a series of blog posts are solutions to some problems that i have encounter while i am trying to finish my first iOS app. The one hurdle that I have encountered while getting started on developing an iOS app is that a majority of the solutions for iOS specific problems provide solutions to them using Objective-C. Which is fair, because Swift has not been around for that long. Anyway let us get started with a few basics, A few basics I would highly recommend having a read of this book...