Cart Context Reducer - React, Vite, JavaScript, Context API, useReducer, Map, Custom CSS Fundamental Project 14
A learning-focused shopping cart application built with React. It demonstrates useReducer for predictable state updates, the Context API for global state, and a JavaScript Map for efficient cart storage. Use it to understand intermediate React patterns and how to structure a small e-commerce-style UI without external state libraries.
- Live Demo: https://cart-context-reducer.vercel.app/
- Project Summary
- Features
- Technology Stack
- Project Structure
- Installation & Running
- Environment Variables (.env)
- Application Walkthrough
- Global State & useReducer
- API & Data Management
- Component Overview & Reusability
- Key Code Examples
- Keywords & Learning Outcomes
- Conclusion
- License
This app is a single-page shopping cart that loads product data from an external API, stores the cart in a Map, and updates state via useReducer and Context. There is no backend; all logic runs in the browser. It is ideal for learning React state patterns, reducer flows, and component composition in a small, readable codebase.
- Cart operations: Add, remove, and update item quantities (increase/decrease).
- Clear cart: One action to remove all items.
- Totals: Automatic subtotal and total item count from the cart Map.
- Initial data: Cart items loaded from a public API on mount.
- Empty state: When the bag is empty, a "Return to default cart" button re-fetches the default list (no page reload).
- UI: Navbar with cart count, list of cart items with controls, and responsive layout with custom CSS.
| Technology | Purpose |
|---|---|
| React 18 | UI components and hooks |
| JavaScript (ES6+) | No TypeScript; plain JS with modules |
| Vite 4 | Dev server, build, and HMR |
| Context API | Global cart state and actions |
| useReducer | Cart state transitions (clear, remove, increase, decrease, load) |
| Map | In-memory cart storage keyed by item id |
| react-icons | FaCartPlus, FaChevronUp, FaChevronDown |
| Custom CSS | Global and component styles in index.css |
| ESLint | Linting with React and React Hooks plugins |
14-cart/
├── .gitignore
├── LICENSE
├── README.md
├── index.html # Entry HTML, meta tags, root div
├── package.json
├── package-lock.json
├── vite.config.js # Vite + React plugin
├── eslint.config.js # ESLint flat config (React, hooks, refresh)
├── public/
│ └── vite.svg # Favicon / app icon
└── src/
├── main.jsx # React root, AppProvider, StrictMode
├── App.jsx # Layout: loading spinner or Navbar + CartContainer
├── index.css # Global + navbar, cart, cart-item styles
├── context.jsx # AppContext, AppProvider, useGlobalContext, fetchData
├── reducer.js # cartReducer (CLEAR_CART, REMOVE, INCREASE, DECREASE, LOADING, DISPLAY_ITEMS)
├── actions.js # Action type constants
├── utils.js # getTotals(cart) → { totalAmount, totalCost }
├── data.jsx # Fallback/static cart items (optional; API is primary)
├── Navbar.jsx # Top bar with cart icon and total amount
├── CartContainer.jsx # Cart list or empty state + "Return to default cart"
└── CartItem.jsx # Single row: image, title, price, amount controls, removeRoutes: There are no client-side routes; the app is a single view (navbar + cart content).
1. Clone the repository
git clone https://github.com/arnobt78/Cart--React-Fundamental-Project-14.git
cd Cart--React-Fundamental-Project-142. Install dependencies
npm install3. Start the development server
npm run devOpen the URL shown in the terminal (e.g. http://localhost:5173).
4. Other scripts
| Command | Description |
|---|---|
npm run build |
Production build to dist/ |
npm run preview |
Serve the production build locally |
npm run lint |
Run ESLint |
npm run lint:fix |
Run ESLint with auto-fix |
This project does not require any environment variables to run. The cart API URL is set directly in src/context.jsx:
const url = "https://www.course-api.com/react-useReducer-cart-project";If you want to make the API URL configurable (e.g. for different environments or a future backend), you can use Vite’s env system:
1. Create a .env file in the project root (optional):
VITE_CART_API_URL=https://www.course-api.com/react-useReducer-cart-project2. Use it in code (e.g. in context.jsx):
const url =
import.meta.env.VITE_CART_API_URL ||
"https://www.course-api.com/react-useReducer-cart-project";Notes:
- Only variables prefixed with
VITE_are exposed to the client. - Restart the dev server after changing
.env. - Do not commit secrets;
.envis typically in.gitignore(this repo already ignores.env).
-
Load the app
The app fetches cart data from the API. A loading spinner is shown until the request completes. -
View the cart
You see a list of items (e.g. phones) with image, title, price, quantity, and controls. -
Change quantity
Use the up/down chevrons to increase or decrease the amount. At 1, decrease removes the item. -
Remove an item
Click "remove" on a row to delete that item from the cart. -
Clear cart
Click "clear cart" to empty the bag. -
Empty state
When the cart is empty, the message "your bag / is currently empty" and a "Return to default cart" button are shown. Clicking it re-fetches the default list without reloading the page. -
Navbar
The top bar shows a cart icon and the total number of items in the cart.
All cart state lives in React Context and is updated with useReducer.
-
Context (
context.jsx):AppContextholds cart state and actions.AppProviderwraps the app and runs the reducer; it also fetches initial data and exposesclearCart,remove,increase,decrease,totalAmount,totalCost, andresetCart(re-fetch). -
Reducer (
reducer.js): Handles action types fromactions.jsand returns the next state. The cart is a Map; each action either replaces the Map or returns a new Map (no mutation).
Action types:
| Action | Purpose |
|---|---|
CLEAR_CART |
Empty the cart (new Map). |
REMOVE |
Remove one item by id. |
INCREASE |
Add 1 to item amount. |
DECREASE |
Subtract 1; remove item if amount was 1. |
LOADING |
Set loading true before fetch. |
DISPLAY_ITEMS |
Set cart from API array (converted to Map). |
Reducer example (clear and remove):
if (action.type === CLEAR_CART) {
return { ...state, cart: new Map() };
}
if (action.type === REMOVE) {
const newCart = new Map(state.cart);
newCart.delete(action.payload.id);
return { ...state, cart: newCart };
}-
Backend: None. The app is frontend-only.
-
External API: One read-only endpoint is used for initial cart data:
GET
https://www.course-api.com/react-useReducer-cart-projectIt returns a JSON array of items. Each item has at least:
id,title,price,img,amount(or similar). The app converts this array to a Map keyed byidfor O(1) lookups and updates. -
Fetching: Done in
context.jsxinsideAppProvider: on mount,fetchData()dispatchesLOADING, thenfetch(url), thenDISPLAY_ITEMSwith the parsed JSON. The samefetchDatais exposed asresetCartfor the empty-state button.
- Role: Root layout. Reads
loadingfrom context; shows spinner orNavbar+CartContainer. - Reuse: Wrap any “app shell” that depends on the same context in
AppProvider.
- Role: Renders a fixed top bar with a title (“useReducer”) and cart icon + total item count (
totalAmountfrom context). - Reuse: Use in any screen that needs a cart summary; ensure the app is wrapped in the same context so
useGlobalContext()works.
- Role: If cart is empty, shows “your bag / is currently empty” and a “Return to default cart” button. Otherwise renders the list of
CartItemand a footer with total and “clear cart”. - Reuse: Replace the “Return to default cart” action with your own (e.g. redirect or different API). The structure (empty vs list + footer) can be reused for any cart UI that uses the same context shape.
- Role: One row per item: image, title, price, amount controls (up/down), and remove. Uses
useGlobalContext()forremove,increase,decrease. - Reuse: Use in other projects by keeping the same props (
id,img,title,price,amount) and the same context methods, or pass callbacks as props for a more generic component.
- Role: Defines
AppContext,AppProvider, anduseGlobalContext. Holds cart state, totals, and all cart actions includingresetCart. - Reuse: Copy
AppProviderand the reducer/actions/utils into another app to get the same cart logic; swap the API URL or add.envas above.
- Role: Centralized action types and pure reducer for the cart Map.
- Reuse: Use the same pattern (action constants + reducer) in any React app that needs reducer-based cart state.
- Role:
getTotals(cart)iterates the Map and returns{ totalAmount, totalCost }. - Reuse: Use in any place you have a Map (or array) of items with
amountandprice.
const cart = new Map();
cart.set("itemId", { id: "itemId", title: "Product", price: 9.99, amount: 2 });
cart.get("itemId"); // { id, title, price, amount }
cart.delete("itemId");
for (const [id, item] of cart) {
console.log(id, item);
}const items = await response.json();
const newCart = new Map(items.map((item) => [item.id, item]));const { cart, remove, increase, decrease, totalAmount } = useGlobalContext();
const cartArray = Array.from(cart.entries());dispatch({ type: CLEAR_CART });
dispatch({ type: REMOVE, payload: { id: "rec123" } });
dispatch({ type: INCREASE, payload: { id: "rec123" } });
dispatch({ type: DECREASE, payload: { id: "rec123" } });
dispatch({ type: DISPLAY_ITEMS, payload: { cart: apiCartArray } });export const getTotals = (cart) => {
let totalAmount = 0;
let totalCost = 0;
for (const { amount, price } of cart.values()) {
totalAmount += amount;
totalCost += amount * price;
}
return { totalAmount, totalCost };
};- React: Hooks (
useReducer,useContext,useEffect), Context API, component composition. - State: Reducer pattern, action types, immutable updates, Map as state.
- Data structures: JavaScript
Map,Array.from, iterators. - API:
fetch, async/await, loading state. - Concepts: E-commerce cart logic, global state without Redux, custom hooks (
useGlobalContext).
This project is a small, teachable example of a React cart: Context for global state, useReducer for updates, and a Map for the cart. You can extend it with local storage, a real backend, or product listing and search. Use the structure and code snippets above to adapt the components and patterns to your own apps.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊

