In short
An online store is the classic React project because it uses almost everything: lists, routing, shared cart state and forms. Build a small one properly and most React apps will feel familiar.
A product listing, cart and checkout flow — one of the most common real-world React apps, combining state management, routing and forms.

Think of it like this
A supermarket trip
You walk the aisles (product list), pick something up to read the label (product page), drop it in the trolley (cart) and pay at the counter (checkout). Your app needs each of these stations, and the trolley has to follow you everywhere.
Watch it happen
Add to cart, everywhere at once
{type: "add", item}
1/4The cart has one ₹20 pen and one ₹60 notebook. You click Add on the pen again.
The big ideas
What you need to know
Product list and filters
Show products from a JSON file or a free API, with a search box and a category filter.
A cart that's everywhere
Keep cart state in Context (or Zustand later) so the header badge and the cart page always agree.
Real routes
/, /product/:id, /cart and /checkout, linked with Link so the cart survives every page change.
Derived totals
Work out item count and total from the cart array. Don't store them separately.
How it works
Build plan
- 1
Static product grid
Render products from a local JSON file with a ProductCard component.
- 2
Add routing
Create the product detail page and read the id with useParams.
- 3
Cart state
Add, remove and change quantities from one shared cart.
- 4
Checkout form
Collect address details and show an order summary.
- 5
Save the cart
Store it in localStorage so a refresh doesn't empty it.
See it in code
Walk through the code
1type Item = { id: number; name: string; price: number; qty: number };2type Action = { type: "add"; item: Omit<Item, "qty"> } | { type: "remove"; id: number };3 4export function cartReducer(cart: Item[], action: Action): Item[] {5 switch (action.type) {6 case "add": {7 const found = cart.find((i) => i.id === action.item.id);8 if (found) return cart.map((i) => (i.id === found.id ? { ...i, qty: i.qty + 1 } : i));9 return [...cart, { ...action.item, qty: 1 }];10 }11 case "remove":12 return cart.filter((i) => i.id !== action.id);13 }14}15 16// const total = cart.reduce((sum, i) => sum + i.price * i.qty, 0);1/4Types for one cart line and the two actions the reducer understands.
Use it with useReducer(cartReducer, []). Adding the same product twice bumps its quantity instead of creating a duplicate line.
Save yourself the pain
Common mistakes
Oops
Keeping cartTotal in its own state and updating it by hand.
Do this instead
Calculate the total from the cart on every render. One source of truth, no mismatched numbers.
Oops
Storing prices like 19.99 and getting totals like 0.30000000000000004.
Do this instead
Store prices as whole numbers (paise), and format them with Intl.NumberFormat only when showing them.
Oops
Trusting the price the browser sends at checkout.
Do this instead
A real store looks prices up on the server. For this project, at least remember that client prices can be edited.
Your mission
First milestone: a working cart
Quick check
The cart has [{ price: 500, qty: 2 }, { price: 200, qty: 1 }]. What's the total, and where should it come from?
Go deeper
- 1Manage cart state with Context or a lightweight state library.
- 2Implement product filtering/sorting and a persistent cart (localStorage).
- 3Build a multi-step checkout form with validation.
More projects at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.