Skip to content

VSCRM/React-Shop

Repository files navigation

React Shop β€” Production-Ready TypeScript Refactor

A full-stack e-commerce React application refactored to be production-ready, fully type-safe, and 100% tested with Vitest and React Testing Library.

Live demo: vscrm.github.io/React-Shop Backend repository: React-Shop-Backend


Table of Contents

  1. Pages
  2. Tech Stack
  3. Quick Start
  4. Environment Variables
  5. Running the Test Suite
  6. Architectural Changes
  7. Zod Integration β€” Runtime Validation
  8. Component & Module Breakdown
  9. Project Structure
  10. Added & Changed Dependencies
  11. Type Safety Guidelines
  12. Test Coverage Map
  13. Acknowledgements
  14. License

πŸ“Έ Pages

Page Route Description
Home / Product catalog with search and add-to-cart
Checkout /checkout Cart review, delivery options, order placement
Orders /orders Order history with buy-again functionality
Tracking /tracking/:orderId/:productId? Real-time delivery tracking progress

πŸš€ Tech Stack

Core

Technology Version Purpose
React 19 UI library with the latest concurrent features
TypeScript 6 Static typing with strict mode enabled
React Router 7 Client-side routing with BrowserRouter and basename support
Vite 8 Build tool and dev server with HMR

Data & Validation

Library Version Purpose
Axios 1.x HTTP client for all API requests via a shared api.ts instance
Zod 3.x Runtime schema validation and TypeScript type inference ✨ New
Day.js 1.x Lightweight date formatting for delivery and order dates

Testing

Library Purpose
Vitest Unit test runner (Vite-native, Jest-compatible)
@testing-library/react Component rendering and querying in tests
@testing-library/user-event Simulates real user interactions
@testing-library/jest-dom Custom DOM matchers (toBeInTheDocument, etc.)
jsdom Browser environment simulation for Node.js

Dev & Deployment

Tool Purpose
ESLint Code linting with React Hooks and React Refresh plugins
gh-pages Automated deployment to GitHub Pages from the dist folder

Quick Start

# Install project dependencies
npm install

# Run linter to check for code style and errors
npm run lint

# Start development server (proxies /api and /images to localhost:3000)
npm run dev

# Run tests once
npm run test

# Run tests in watch mode for development
npm run test:watch

# Run tests and generate code coverage report
npm run test:coverage

# Build production-ready assets into the dist folder
npm run build

# Locally preview the production build
npm run preview

# Build and deploy the application to GitHub Pages
npm run deploy

βš™οΈ Environment Variables

Create a .env file in the project root for local development:

VITE_API_URL=http://localhost:3000

.env.production is committed and used automatically during npm run build / npm run deploy:

VITE_API_URL=https://react-shop-backend-jg62.onrender.com

Running the Test Suite

Command Description
npm test Run all tests once (CI mode)
npm run test:watch Run in watch mode (development)
npm run test:coverage Run with V8 coverage report

Expected output

 βœ“ src/schemas/index.test.ts              (56 tests)
 βœ“ src/hooks/useProducts.test.ts          (14 tests)
 βœ“ src/hooks/useCartActions.test.tsx      (12 tests)
 βœ“ src/services/api.test.ts               (12 tests)
 βœ“ src/hooks/useDeliveryOptions.test.ts   (11 tests)
 βœ“ src/hooks/useBuyAgain.test.ts          (11 tests)
 βœ“ src/pages/home/ProductCard.test.tsx    (11 tests)
 βœ“ src/components/ErrorBoundary.test.tsx  (8 tests)
 βœ“ src/hooks/useCartItemHandlers.test.ts  (8 tests)
 βœ“ src/hooks/useCartData.test.ts          (8 tests)
 βœ“ src/hooks/useHeaderSearch.test.tsx     (10 tests)
 βœ“ src/context/CartProvider.test.tsx      (3 tests)
 βœ“ src/hooks/useFlashMessage.test.ts      (6 tests)
 βœ“ src/utils/getTrackedProducts.test.ts   (6 tests)
 βœ“ src/hooks/useCartContext.test.tsx      (3 tests)
 βœ“ src/hooks/useDebounce.test.ts          (5 tests)
 βœ“ src/utils/computeStatus.test.ts        (7 tests)
 βœ“ src/utils/searchUtils.test.ts          (6 tests)
 βœ“ src/utils/money.test.ts                (6 tests)

 Test Files  19 passed                    (19)
       Tests 203 passed                   (203)

Architectural Changes

1. Zod as the Single Source of Truth

Before: TypeScript interfaces in src/types/index.ts were manually defined and duplicated runtime shape expectations.

After: All domain types are derived from Zod schemas via z.infer:

src/schemas/index.ts   ← Zod schemas (runtime validation)
       ↓ z.infer
src/types/index.ts     ← TypeScript types (static typing)

Change a schema once β€” both runtime validation and static types update automatically.

2. Runtime Validation at API Boundaries

Every data-fetching function now calls Schema.parse(response.data) before returning. If the server sends unexpected data (missing fields, wrong types, out-of-range values), a ZodError is thrown immediately and the hook's catch block converts it to a user-facing error message.

Affected services:

Service Schema used
loadToCart CartItemListSchema
createOrder CreatedOrderSchema

Affected hooks:

Hook Schema used
useProducts ProductListSchema
useDeliveryOptions DeliveryOptionListSchema
useOrders OrderListSchema
useOrder OrderSchema
usePaymentSummary PaymentSummarySchema

3. Strict TypeScript Configuration

tsconfig.json enforces:

{
	"strict": true,
	"noUnusedLocals": true,
	"noUnusedParameters": true,
	"noFallthroughCasesInSwitch": true
}

All function arguments, return types, React component props, custom hook return types, and event handlers are explicitly typed. any and unknown are banned from application code β€” Zod handles the unknown β†’ validated type boundary.

4. Explicit Hook Interfaces

useProducts (and all other data-fetching hooks) now return an explicit result interface, making the contract clear to consumers without reading the implementation.

5. JSDoc on All Public APIs

Every exported function, hook, schema, and type has a JSDoc comment documenting its purpose, parameters, return value, and possible thrown errors (@throws).


Zod Integration β€” Runtime Validation

Schema file: src/schemas/index.ts

RatingSchema               β†’ { stars: 0–5, count: int β‰₯ 0 }
ProductSchema              β†’ a single product
ProductListSchema          β†’ z.array(ProductSchema)
DeliveryOptionSchema       β†’ a delivery option
DeliveryOptionListSchema   β†’ z.array(DeliveryOptionSchema)
CartItemSchema             β†’ a cart item (embeds ProductSchema)
CartItemListSchema         β†’ z.array(CartItemSchema)
OrderProductSchema         β†’ a product inside an order
OrderSchema                β†’ a full order (embeds OrderProductSchema[])
OrderListSchema            β†’ z.array(OrderSchema)
CreatedOrderSchema         β†’ POST /api/orders response
PaymentSummarySchema       β†’ checkout cost breakdown

Usage pattern in services

// Before β€” no runtime validation
const response = await api.get<CartItem[]>("/api/cart-items?expand=product");
return response.data; // trusted blindly

// After β€” Zod validates before returning
const response = await api.get<unknown>("/api/cart-items?expand=product");
return CartItemListSchema.parse(response.data); // throws ZodError on bad data

Handling Zod errors in hooks

All data-fetching hooks catch both network errors and Zod parse errors in the same catch block:

try {
	const validated = SomeSchema.parse(response.data);
	setData(validated);
} catch {
	// Catches ZodError (bad API data) AND AxiosError (network failure)
	setError("Could not load data. Please try again.");
}

Component & Module Breakdown

src/schemas/

File Responsibility
index.ts All Zod validation schemas β€” single source of truth
index.test.ts 56 tests: valid data, invalid data, edge cases for every schema

src/types/

File Responsibility
index.ts TypeScript types derived from schemas via z.infer + UI-only types

src/services/

File Method Endpoint Zod validation
api.ts β€” Axios instance factory β€”
addToCart.ts POST /api/cart-items No (mutation)
loadToCart.ts GET /api/cart-items?expand=product βœ… CartItemListSchema
updateCartQuantity.ts PUT /api/cart-items/:id No (mutation)
updateCartDelivery.ts PUT /api/cart-items/:id No (mutation)
deleteCartItem.ts DELETE /api/cart-items/:id No (mutation)
createOrder.ts POST /api/orders βœ… CreatedOrderSchema

src/hooks/

Hook Responsibility
useCart Composes useCartData + useCartActions into the full cart API
useCartData Raw cart state: initial load, setCart, manual loadCart
useCartActions All cart mutation operations (add, update, remove, order)
useCartContext Type-safe access to CartContext; throws if used outside provider
useCartItemHandlers UI state for a single checkout row (editing, delete confirm)
useProducts Products list with search, loading & error states + Zod validation
useDeliveryOptions Delivery options fetch + Zod validation
useOrders Orders list fetch + Zod validation
useOrder Single order fetch by ID + Zod validation
usePaymentSummary Payment summary fetch triggered by cart changes + Zod validation
useBuyAgain "Buy again" picker state for the orders page
useFlashMessage Timed boolean flag (e.g. "Added to cart" flash)
useDebounce Generic debounce for search input
useHeaderSearch Header search bar: input, submit, clear, URL sync

src/context/

File Responsibility
CartContext.tsx Creates the CartContext with CartContextValue | undefined
CartProvider.tsx Wraps children with the cart context populated from useCart

src/components/

File Responsibility
ErrorBoundary.tsx Class component error boundary; supports custom fallback render-prop
HeaderLogo.tsx Site logo with responsive images
HeaderNav.tsx Navigation links + cart item count
HeaderSearch.tsx Search bar composition (SearchInput + SearchButton)
SearchInput.tsx Controlled input element
SearchButton.tsx Submit / clear toggle button

src/utils/

File Responsibility
constants.ts App-wide constants (search key, debounce delay, tracking steps)
computeStatus.ts Derives TrackingStatus from timestamps
getTrackedProducts.ts Filters order products by optional productId
imageUrl.ts Builds absolute URLs for API images and static assets
money.ts formatMoney(cents) β†’ "$X.XX"
searchUtils.ts buildSearchPath(value) β†’ URL string

πŸ—οΈ Project Structure

React-Shop/
β”œβ”€β”€ public/                                # Static assets served as-is by Vite (not bundled).
β”‚   β”‚                                      # Contains UI icons and logo images used directly
β”‚   β”‚                                      # in JSX via staticImage() helper.
β”‚   β”‚                                      # Product images and ratings are served by the
β”‚   β”‚                                      # backend (Render.com) and are not included here.
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ ErrorBoundary.test.tsx
β”‚   β”‚   β”œβ”€β”€ ErrorBoundary.tsx
β”‚   β”‚   β”œβ”€β”€ HeaderLogo.tsx
β”‚   β”‚   β”œβ”€β”€ HeaderNav.tsx
β”‚   β”‚   β”œβ”€β”€ HeaderSearch.tsx
β”‚   β”‚   β”œβ”€β”€ SearchButton.tsx
β”‚   β”‚   └── SearchInput.tsx
β”‚   β”œβ”€β”€ context/
β”‚   β”‚   β”œβ”€β”€ CartContext.tsx
β”‚   β”‚   └── CartProvider.test.tsx
β”‚   β”‚   └── CartProvider.tsx
β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”œβ”€β”€ useBuyAgain.test.ts
β”‚   β”‚   β”œβ”€β”€ useBuyAgain.ts
β”‚   β”‚   β”œβ”€β”€ useCart.ts
β”‚   β”‚   β”œβ”€β”€ useCartActions.test.tsx         # REFACTORED
β”‚   β”‚   β”œβ”€β”€ useCartActions.ts
β”‚   β”‚   β”œβ”€β”€ useCartContext.test.tsx
β”‚   β”‚   β”œβ”€β”€ useCartData.test.ts             # REFACTORED
β”‚   β”‚   β”œβ”€β”€ useCartData.ts
β”‚   β”‚   β”œβ”€β”€ useCartItemHandlers.test.ts
β”‚   β”‚   β”œβ”€β”€ useCartItemHandlers.ts
β”‚   β”‚   β”œβ”€β”€ useDebounce.test.ts
β”‚   β”‚   β”œβ”€β”€ useDebounce.ts
β”‚   β”‚   β”œβ”€β”€ useDeliveryOptions.test.ts      # REFACTORED
β”‚   β”‚   β”œβ”€β”€ useDeliveryOptions.ts           # now validates with Zod ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ useFlashMessage.test.ts
β”‚   β”‚   β”œβ”€β”€ useFlashMessage.ts
β”‚   β”‚   β”œβ”€β”€ useHeaderSearch.test.tsx
β”‚   β”‚   β”œβ”€β”€ useHeaderSearch.ts
β”‚   β”‚   β”œβ”€β”€ useOrder.ts                     # now validates with Zod ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ useOrders.ts                    # now validates with Zod ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ usePaymentSummary.ts            # now validates with Zod ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ useProducts.test.ts             # REFACTORED
β”‚   β”‚   └── useProducts.ts                  # now validates with Zod ✨ REFACTORED
β”‚   β”œβ”€β”€ layout/
β”‚   β”‚   β”œβ”€β”€ Header.css
β”‚   β”‚   └── Header.tsx
β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ checkout/
β”‚   β”‚   β”‚   β”œβ”€β”€ CartItemActions.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ CartItemDetails.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ CheckoutHeader.css
β”‚   β”‚   β”‚   β”œβ”€β”€ CheckoutHeader.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ CheckoutPage.css
β”‚   β”‚   β”‚   β”œβ”€β”€ CheckoutPage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ DeleteConfirm.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ DeliveryDate.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ DeliveryOptions.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ OrderSummary.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ PaymentSummary.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ QuantityDisplay.tsx
β”‚   β”‚   β”‚   └── QuantityEditor.tsx
β”‚   β”‚   β”œβ”€β”€ home/
β”‚   β”‚   β”‚   β”œβ”€β”€ AddedMessage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ HomePage.css
β”‚   β”‚   β”‚   β”œβ”€β”€ HomePage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ NoSearchResults.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductCard.test.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductCard.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductQuantity.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductRating.tsx
β”‚   β”‚   β”‚   └── ProductsGrid.tsx
β”‚   β”‚   β”œβ”€β”€ order/
β”‚   β”‚   β”‚   β”œβ”€β”€ BuyAgainPicker.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ OrderContainer.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ OrderDetail.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ OrderHeader.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ OrdersPage.css
β”‚   β”‚   β”‚   β”œβ”€β”€ OrdersPage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductActions.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductDetails.tsx
β”‚   β”‚   β”‚   └── ProductInfo.tsx
β”‚   β”‚   └── tracking/
β”‚   β”‚       β”œβ”€β”€ TrackingDeliveryHeader.tsx
β”‚   β”‚       β”œβ”€β”€ TrackingItem.tsx
β”‚   β”‚       β”œβ”€β”€ TrackingPage.css
β”‚   β”‚       β”œβ”€β”€ TrackingPage.tsx
β”‚   β”‚       β”œβ”€β”€ TrackingProductInfo.tsx
β”‚   β”‚       β”œβ”€β”€ TrackingProgress.tsx
β”‚   β”‚       └── TrackingStatusMessage.tsx
β”‚   β”œβ”€β”€ schemas/
β”‚   β”‚   β”œβ”€β”€ index.test.ts                   # 56 schema tests ✨ NEW
β”‚   β”‚   └── index.ts                        # Zod schemas (single source of truth) ✨ NEW
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ addToCart.ts
β”‚   β”‚   β”œβ”€β”€ api.test.ts                     # includes Zod rejection tests ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ api.ts
β”‚   β”‚   β”œβ”€β”€ createOrder.ts                  # now validates with CreatedOrderSchema ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ deleteCartItem.ts
β”‚   β”‚   β”œβ”€β”€ loadToCart.ts                   # now validates with CartItemListSchema ✨ REFACTORED
β”‚   β”‚   β”œβ”€β”€ updateCartDelivery.ts
β”‚   β”‚   └── updateCartQuantity.ts
β”‚   β”œβ”€β”€ types/
β”‚   β”‚   └── index.ts                        # z.infer-derived types ✨ REFACTORED
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   β”œβ”€β”€ computeStatus.test.ts
β”‚   β”‚   β”œβ”€β”€ computeStatus.ts
β”‚   β”‚   β”œβ”€β”€ constants.ts
β”‚   β”‚   β”œβ”€β”€ getTrackedProducts.test.ts
β”‚   β”‚   β”œβ”€β”€ getTrackedProducts.ts
β”‚   β”‚   β”œβ”€β”€ imageUrl.ts
β”‚   β”‚   β”œβ”€β”€ money.test.ts
β”‚   β”‚   β”œβ”€β”€ money.ts
β”‚   β”‚   β”œβ”€β”€ searchUtils.test.ts
β”‚   β”‚   └── searchUtils.ts
β”‚   β”œβ”€β”€ App.tsx
β”‚   β”œβ”€β”€ index.css
β”‚   β”œβ”€β”€ main.tsx
β”‚   └── vite-env.d.ts                       # <reference types="vite/client" />
β”œβ”€β”€ .editorconfig
β”œβ”€β”€ .env                                    # Local development API URL (git-ignored)
β”œβ”€β”€ .env.example                            # Template for environment variables
β”œβ”€β”€ .env.production                         # Production API URL (committed)
β”œβ”€β”€ .gitignore
β”œβ”€β”€ eslint.config.ts
β”œβ”€β”€ index.html
β”œβ”€β”€ jsconfig.json
β”œβ”€β”€ LICENSE
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ package.json
β”œβ”€β”€ README.md
β”œβ”€β”€ setupTests.ts                           # Imports jest-dom matchers for all tests
β”œβ”€β”€ tsconfig.json                           # TypeScript config with strict mode and path aliases
β”œβ”€β”€ tsconfig.node.json
└── vite.config.ts                          # Build config, proxy, base path, test env

Added & Changed Dependencies

New production dependency

Package Version Reason
zod ^3.24.2 Runtime schema validation and TypeScript type inference

Updated package.json scripts

{
	"test": "vitest run",
	"test:watch": "vitest",
	"test:coverage": "vitest run --coverage"
}

No removed dependencies

All existing dependencies (axios, dayjs, react-router, etc.) are unchanged.


Type Safety Guidelines

βœ… Do

// Derive types from schemas
import type { z } from 'zod';
import { ProductSchema } from '@/schemas';
type Product = z.infer<typeof ProductSchema>;

// Explicitly type all function signatures
function formatMoney(amountCents: number): string { ... }

// Use schema.parse() at API boundaries
const data = ProductListSchema.parse(response.data);

// Use schema.safeParse() when you need to handle errors gracefully
const result = CartItemSchema.safeParse(raw);
if (!result.success) console.error(result.error.format());

❌ Don't

// Never use `any`
const data: any = response.data;

// Never trust raw API responses without parsing
return response.data as CartItem[];

// Never duplicate type definitions manually when a schema already exists
interface Product {
	id: string; /* ... */
}

Test Coverage Map

Test File What it covers
schemas/index.test.ts βœ… Valid data accepted Β· ❌ Invalid data rejected Β· πŸ”² Edge cases (zero prices, boundary stars, empty arrays)
services/api.test.ts HTTP methods & URLs Β· Zod rejection from loadToCart Β· Zod rejection from createOrder
hooks/useProducts.test.ts Valid fetch Β· Zod rejects malformed data Β· Loading states Β· Re-fetch on query change
hooks/useDeliveryOptions.test.ts Valid fetch Β· Zod rejects malformed data Β· Loading/error states
hooks/useCartData.test.ts Initial load Β· Zod/network error β†’ cartError Β· Manual reload Β· Optimistic setCart
hooks/useCartActions.test.tsx addCart Β· updateDeliveryOption Β· updateQuantity Β· removeItem Β· placeOrder Β· error swallowing
hooks/useCartItemHandlers.test.ts isEditing toggle Β· handleSaveQuantity Β· handleDeleteOne (qty=1 vs qty>1) Β· handleDeleteAll
hooks/useCartContext.test.tsx Value returned Β· Same reference Β· Throws outside provider
hooks/useBuyAgain.test.ts Initial state Β· Single-qty direct add Β· Multi-qty picker flow Β· confirm Β· cancel
hooks/useFlashMessage.test.ts Inactive initially Β· Active after trigger Β· Auto-deactivates Β· Timer reset
hooks/useDebounce.test.ts Initial value Β· No update before delay Β· Updates after delay Β· Rapid change debounce
hooks/useHeaderSearch.test.tsx Initial state Β· Home page URL sync Β· Input change Β· Clear Β· Form submit
context/CartProvider.test.tsx Cart data provided Β· Context shape Β· Children rendered
components/ErrorBoundary.test.tsx Normal render Β· Default fallback UI Β· Custom fallback prop Β· Try-again reset Β· logging
pages/home/ProductCard.test.tsx Name/price/image render Β· Add to cart Β· Quantity selector Β· Flash message
utils/computeStatus.test.ts Delivered (at/past time) Β· Preparing (<40%) Β· Shipped (40–99%)
utils/getTrackedProducts.test.ts null order Β· no products Β· filter by id Β· all products
utils/money.test.ts Various cent values Β· Dollar sign Β· 2 decimal places
utils/searchUtils.test.ts Empty/whitespace β†’ / Β· Query path Β· URL encoding Β· Trim

πŸ™ Acknowledgements

This project was built while following the SuperSimpleDev React course β€” a fantastic, hands-on resource for learning modern React from scratch.


πŸ“„ License

License: MIT

About

πŸ›οΈ E-commerce SPA built with React 19, React Router 7 and Vite. Features product catalog, cart, checkout, order history and delivery tracking.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages