A fully functional e-commerce course platform clone built with React β featuring category browsing, detailed course pages, and a persistent shopping cart powered by Context API + useReducer.
Udemy.mp4
Udemy Clone is a React SPA that replicates the core UI and shopping experience of the Udemy platform. Users can browse courses by category, view detailed course pages with ratings, syllabi and instructor info, add items to a cart, and proceed to checkout β all with a persistent cart that survives page refreshes via localStorage.
The project was built to practice advanced React patterns, specifically global state management with Context API and useReducer, client-side routing with dynamic parameters, and component styling with Styled Components.
- π Home Page β Hero slider, featured course list, and category grid
- π Category Filtering β Dynamic route per category rendering filtered course cards
- π Single Course Page β Full course detail view: description, star rating, syllabus, what you'll learn, pricing and add-to-cart
- π Shopping Cart β Add, remove and clear items, with total price calculation
- πΎ Persistent Cart β Cart state is saved to localStorage and restored on page load
- π± Responsive Layout β CSS Grid breakpoints from 1 to 4 columns adapting to screen size
- π Hero Carousel β Auto-play image slider built with react-slick
Frontend:
- React 18 β Component architecture, hooks (
useState,useEffect,useReducer,useContext) - React Router DOM v6 β Multi-page routing with dynamic segments (
/courses/:id,/category/:category) - Styled Components v6 β Component-scoped CSS-in-JS styling with theming variables
- React Slick β Hero banner carousel
- React Icons β Icon library used across UI elements
State Management:
- Context API β Three isolated contexts:
CartContext,CoursesContext,SidebarContext useReducerβ Predictable state transitions for cart operations and course data
Build Tool:
- Vite β Fast dev server and optimized production build
# Clone the repository
git clone https://github.com/Luan-Neumann-Dev/udemy.git
# Navigate to project
cd udemy
# Install dependencies
npm install
# Start the dev server
npm run devThen open http://localhost:5173 in your browser.
Or visit the live demo β
udemy/
βββ src/
β βββ components/ # Reusable UI components
β β βββ Navbar.jsx # Top bar with cart badge and sidebar toggle
β β βββ Sidebar.jsx # Mobile navigation drawer
β β βββ Hero.jsx # Homepage banner carousel
β β βββ Course.jsx # Course card (used in lists)
β β βββ CourseList.jsx # Featured courses section
β β βββ CategoriesList.jsx
β β βββ CartItem.jsx # Single item row in cart
β β βββ StarRating.jsx # Visual star rating component
β β βββ Tabs.jsx
β β βββ Loader.jsx
β β βββ Footer.jsx
β βββ pages/ # Route-level page components
β β βββ HomePage.jsx
β β βββ CoursesPage.jsx # Filtered by category
β β βββ SingleCoursePage.jsx
β β βββ CartPage.jsx
β βββ context/ # Global state providers
β β βββ cartContext.jsx
β β βββ coursesContext.jsx
β β βββ sidebarContext.jsx
β βββ reducers/ # Pure reducer functions
β β βββ cartReducer.js
β β βββ coursesReducer.js
β β βββ sidebarReducer.js
β βββ utils/
β β βββ data.js # Static course dataset
β β βββ images.js # Centralized image imports
β β βββ constants.js
β βββ actions.js # Action type constants
Instead of prop drilling, all shared state is managed through three independent contexts. Each follows the same pattern: a context, a provider with useReducer, and a custom hook for consumption.
// Dispatching a cart action
const addToCart = (courseID, image, course_name, creator, discounted_price, category) => {
dispatch({ type: ADD_TO_CART, payload: {
courseID, image, course_name, creator, discounted_price, category
}})
}The cart initializes from localStorage and syncs on every state change via useEffect, so items survive full page reloads without any backend.
const loadCartFromStorage = () => {
let cart = localStorage.getItem('cart');
return cart ? JSON.parse(cart) : [];
}
useEffect(() => {
dispatch({ type: GET_CART_TOTAL })
localStorage.setItem('cart', JSON.stringify(state.cart))
}, [state.cart])Before adding an item, the reducer checks for an existing entry by ID β preventing duplicates without needing extra UI logic.
if (action.type === ADD_TO_CART) {
const exists = state.cart.filter(item => item.courseID === action.payload.courseID);
if (exists.length < 1) {
return { ...state, cart: [...state.cart, action.payload] }
}
return { ...state }
}Technical Skills:
- Structuring multiple
useReducer+ Context patterns in a scalable, non-overlapping way - Managing derived state (total items, total amount) reactively inside a reducer
- Using Styled Components for component-scoped styling with CSS variables and media queries
- Navigating dynamic routes with
useParamsto fetch and display contextual data
Best Practices:
- Centralizing all action type strings in a single
actions.jsfile to avoid typos - Separating data-fetching/state logic (context + reducers) from presentational components
- Handling empty states gracefully (empty cart page with redirect CTA)
- Search bar with real-time course filtering
- Wishlist feature
- Checkout flow with order summary
- Course ratings filterable on category page
- Migrate static data to a mock REST API (e.g. JSON Server)
- All course data is static β stored locally in
src/utils/data.js - Not affiliated with Udemy, Inc. This is a frontend practice project
MIT License - see LICENSE for details.
Luan Neumann
- LinkedIn: luan-neumann-dev
- GitHub: @Luan-Neumann-Dev
β Found this helpful? Give it a star!