CSS Frameworks and Redux
Overview
In this vitamin, you'll build a Shopping Cart application using Redux Toolkit for state management and Bootstrap for styling. This teaches you how modern React applications manage complex state across components.
Why Redux? In our React vitamin, you used useState and useContext for state. That works for small apps, but as apps grow, passing state through many components becomes messy. Redux gives you a single central store that any component can read from or write to.
Why Bootstrap? Instead of writing CSS from scratch, Bootstrap provides pre-built classes like btn btn-primary and card that make your app look professional with minimal effort.
Learning Objectives
- Understand Redux core concepts: Store, Slices, Actions, Reducers
- Use
useSelectorto read state from the Redux store - Use
useDispatchto send actions to update state - Apply Bootstrap classes for responsive, professional styling
Redux Concepts Overview
Store
The store is a single JavaScript object that holds all your application's state. Instead of having state scattered across different components, everything lives in one place. Any component can access it.
Slice
A slice is a collection of Redux logic for a single feature. It contains:
- Initial state - the starting values (e.g.,
{ items: [], totalQuantity: 0 }) - Reducers - functions that update the state
Think of slices as organizing your store into sections. A shopping app might have a cartSlice, userSlice, and productsSlice.
Actions
Actions are plain objects that describe what happened. They have a type (like "cart/addItem") and optionally a payload (the data). You don't create these manually; Redux Toolkit generates them for you.
Reducers
Reducers are functions that take the current state and an action, then return the new state. They define how the state changes in response to actions.
addItem: (state, action) => {
// action.payload contains the data passed when dispatching
state.items.push(action.payload);
}
useSelector
The useSelector hook lets components read data from the store. You use this inside your React components (not in the Redux setup files):
// Inside a component like Cart.js
const items = useSelector((state) => state.cart.items);
The function you pass receives the entire store state and returns the piece you need.
useDispatch
The useDispatch hook lets components send actions to update the store. You also use this inside your React components:
// Inside a component like ProductList.js
const dispatch = useDispatch();
dispatch(addItem({ id: 1, name: "Shirt", price: 25 }));
- Slice, reducers, actions →
store/cartSlice.js(Redux setup) - Store configuration →
store/store.js(Redux setup) - useSelector, useDispatch → Inside your React components (e.g.,
Cart.js,ProductList.js)
The Flow
User clicks "Add to Cart"
↓
Component calls dispatch(addItem(product))
↓
Redux runs the addItem reducer
↓
State updates (item added to cart)
↓
Components using useSelector re-render with new data
Using Bootstrap in React
In React, use className instead of class. Combine multiple classes with spaces:
<button className="btn btn-primary">Click me</button>
<div className="card mt-4">...</div>
Bootstrap Classes Reference
Layout:
container-fluid- Full-width containerrow- Creates a horizontal row for columnscol-md-4,col-md-8- Column widths (out of 12 total)mt-4,mb-3,me-2- Margins (t=top, b=bottom, e=end/right, s=start/left)
Cards:
card- Card containercard-header- Card header sectioncard-body- Card content areacard-title- Card title textcard-img-top- Image at top of card
Buttons:
btn- Base button class (always required)btn-primary- Blue buttonbtn-danger- Red buttonbtn-secondary- Gray buttonbtn-sm- Small button size
Lists:
list-group- Container for list itemslist-group-item- Individual list item
Flexbox:
d-flex- Display as flexboxjustify-content-between- Space items apartalign-items-center- Vertically center items
Navbar:
navbar- Navigation bar containernavbar-dark- Light text for dark backgroundsbg-dark- Dark background colornavbar-brand- Brand/title text
Text:
text-muted- Gray/muted text color
See Bootstrap docs for more.
Part 1: Project Setup
npx create-react-app shopping-cart
cd shopping-cart
npm install @reduxjs/toolkit react-redux bootstrap
Create this file structure:
src/
├── components/
│ ├── ProductList.js
│ ├── Cart.js
│ └── CartItem.js
├── store/
│ ├── store.js
│ └── cartSlice.js
├── data/
│ └── products.js
├── App.js
└── index.js
Part 2: Product Data
Create src/data/products.js with an array of at least 4 products. Each product must have: id, name, price, image.
Export this array as the default export.
Part 3: Create the Cart Slice
Create src/store/cartSlice.js:
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
items: [],
totalQuantity: 0,
};
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem: (state, action) => {
// TODO 1: Add item to cart
// If item exists, increment quantity. If new, add with quantity: 1
// Update totalQuantity
},
removeItem: (state, action) => {
// TODO 2: Remove item from cart by id (action.payload)
// Update totalQuantity
},
updateQuantity: (state, action) => {
// TODO 3: Update item quantity
// action.payload has { id, quantity }
// If quantity <= 0, remove item
},
clearCart: (state) => {
// TODO 4: Reset cart to initial state
},
},
});
export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;
export default cartSlice.reducer;
createSlice() returns an object with:
cartSlice.actions- Action creator functions for each reducercartSlice.reducer- The reducer function
We export actions as named exports (for components to dispatch) and the reducer as default export (for the store).
How dispatch works with actions:
When you call an action creator like addItem({ id: 1, name: "Shirt", price: 25 }), it returns an action object:
{ type: 'cart/addItem', payload: { id: 1, name: "Shirt", price: 25 } }
Then dispatch() sends this action to the store, which runs the matching reducer function:
const dispatch = useDispatch();
dispatch(addItem({ id: 1, name: "Shirt", price: 25 }));
// This triggers the addItem reducer with action.payload = { id: 1, name: "Shirt", price: 25 }
With Redux Toolkit, you can write code that looks like it "mutates" state (e.g., state.items.push(item)). Under the hood, it uses Immer to handle immutability for you!
Part 4: Configure the Store
Create src/store/store.js:
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './cartSlice';
// TODO 5: Create and export the store using configureStore
// The store should have a "cart" key that uses cartReducer
Part 5: Connect Redux to React
Update src/index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/store';
import 'bootstrap/dist/css/bootstrap.min.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
Part 6: Create the ProductList Component
Create src/components/ProductList.js:
import { useDispatch } from 'react-redux';
import { addItem } from '../store/cartSlice';
import products from '../data/products';
function ProductList() {
const dispatch = useDispatch();
const handleAddToCart = (product) => {
// TODO 6: Dispatch addItem action with the product
};
return (
<div>
<h2>Products</h2>
{/* TODO 7: Create a grid of product cards */}
{/* Map over products and display each one */}
{/*
Bootstrap classes to use:
- row, col-md-3 (or col-md-4) for grid layout
- card, card-body, card-title, card-img-top for cards
- btn btn-primary for the Add to Cart button
*/}
</div>
);
}
export default ProductList;
Part 7: Create the Cart Component
Create src/components/Cart.js:
import { useSelector, useDispatch } from 'react-redux';
import { clearCart } from '../store/cartSlice';
import CartItem from './CartItem';
function Cart() {
const dispatch = useDispatch();
// TODO 8: Use useSelector to get items from the cart state
// TODO 9: Use useSelector to get totalQuantity from the cart state
const handleClearCart = () => {
// TODO 10: Dispatch clearCart action
};
// TODO 11: Calculate total price (price * quantity for each item)
const totalPrice = 0;
return (
<div>
{/* TODO 12: Build the cart UI */}
{/* Show: "Cart (X items)" title, list of CartItem components, total price, Clear Cart button */}
{/* If cart is empty, show "Your cart is empty" */}
{/*
Bootstrap classes to use:
- card, card-header, card-body for the container
- list-group, list-group-item for the items list
- btn btn-danger for the Clear Cart button
- text-muted for empty cart message
*/}
</div>
);
}
export default Cart;
Part 8: Create the CartItem Component
Create src/components/CartItem.js:
import { useDispatch } from 'react-redux';
import { removeItem, updateQuantity } from '../store/cartSlice';
function CartItem({ item }) {
const dispatch = useDispatch();
const handleIncrease = () => {
// TODO 13: Dispatch updateQuantity to increase by 1
};
const handleDecrease = () => {
// TODO 14: Dispatch updateQuantity to decrease by 1
};
const handleRemove = () => {
// TODO 15: Dispatch removeItem with the item's id
};
return (
<div>
{/* TODO 16: Display item name, price, quantity with +/- buttons, and Remove button */}
{/*
Bootstrap classes to use:
- d-flex, justify-content-between, align-items-center for layout
- btn btn-sm btn-secondary for +/- buttons
- btn btn-sm btn-danger for Remove button
- mb-0 for removing margin from text elements
*/}
</div>
);
}
export default CartItem;
Part 9: Build the App Layout
Update src/App.js:
import ProductList from './components/ProductList';
import Cart from './components/Cart';
function App() {
return (
<div>
{/* TODO 17: Create app layout */}
{/* - Navbar at top with title "Redux Shopping Cart" */}
{/* - Two-column layout: Products on left (wider), Cart on right */}
{/*
Bootstrap classes to use:
- navbar, navbar-dark, bg-dark, navbar-brand for the navbar
- container-fluid for full-width container
- row for the row
- col-md-8 for products column
- col-md-4 for cart column
- mt-4 for top margin
*/}
</div>
);
}
export default App;
Testing Your App
Run npm start and verify:
Cart functionality:
- Adding items to cart increases
totalQuantity - Adding same item twice increases its
quantity(not a duplicate entry) - Quantity +/- buttons update
quantitycorrectly - Quantity going to 0 removes the item
- Remove button removes the item
- Clear Cart resets
itemsandtotalQuantity - Total price calculates correctly (
price * quantityfor each item)
Bootstrap styling:
- Navbar appears dark with white text
- Products display in a grid of cards
- Cart uses card styling with list items
- Buttons have appropriate colors (primary, danger, secondary)
Submission
- Delete
node_modules/folder - Zip your entire project folder
- Submit to Gradescope
We can regenerate node_modules/ by running npm install.