Lecture 1 / 20
Lecture 01 · Fundamentals

Why React & Core Mental Model

Discover what React is, why it became the world's most dominant front-end JavaScript library, how its declarative paradigm differs from traditional DOM manipulation, and how the Virtual DOM optimizes UI rendering.

What You Will Learn
  • The fundamental problem React was created to solve
  • Declarative vs. Imperative programming paradigm
  • Component-based architecture and composability
  • How the Virtual DOM and Reconciliation diffing work under the hood
  • Single Page Applications (SPAs) vs. traditional multi-page websites

01. What is React?

React is a free, open-source JavaScript library developed by Facebook (Meta) in 2013 for building rich, interactive user interfaces. Unlike heavyweight monolithic frameworks, React focuses primarily on the View layer of an application, making it lightweight, flexible, and capable of integrating with any backend or ecosystem.

Before React, web applications relied heavily on direct DOM manipulation using Vanilla JavaScript or jQuery:

imperative-vanilla.js
// The Old Imperative Way: You tell the browser step-by-step HOW to update
const btn = document.getElementById('submit-btn');
const status = document.getElementById('status-msg');

btn.addEventListener('click', () => {
    btn.disabled = true;
    status.innerText = 'Sending request...';
    status.className = 'loading';
    fetchData().then(() => {
        btn.disabled = false;
        status.innerText = 'Success!';
        status.className = 'success';
    });
});

As applications grew in complexity with dozens of widgets, buttons, notifications, and forms, manually updating every element, managing edge cases, and keeping the UI in sync with backend data became a breeding ground for synchronization bugs (spaghetti code).

02. The Declarative Paradigm

React revolutionised front-end development by introducing the declarative mental model: you declare WHAT the user interface should look like for a given state, and React handles HOW to update the browser DOM automatically.

Core Mental Formula

In React, your user interface is purely a function of state:

UI = f(State)

Whenever your state changes, React runs the function again and updates the screen. You never have to manually query or modify DOM nodes.

03. Component-Based Architecture

React structures entire applications as a tree of independent, self-contained, and reusable building blocks called Components. Each component encapsulates its own HTML markup, styling logic, and internal state.

Reusability

Build a component once (e.g. Button, Card, Navbar) and reuse it across multiple screens with different props.

Maintainability

Isolate bugs to a specific component without worrying about unintended side effects across unrelated UI sections.

Composability

Combine small, focused components together like LEGO bricks to assemble massive, feature-rich web platforms.

04. The Virtual DOM & Reconciliation

Directly mutating the browser's Real DOM is computationally expensive. Whenever a DOM element changes, the browser must recalculate page geometry, recompute CSS styles, and perform layout repaints.

React solves this performance bottleneck using an in-memory representation called the Virtual DOM:

  1. Virtual DOM Snapshot: When a component's state changes, React creates an in-memory Virtual DOM tree representing the new UI.
  2. Diffing (Reconciliation): React compares the new Virtual DOM tree with the previous Virtual DOM snapshot using a fast diffing algorithm.
  3. Batch Patching: React computes the minimal set of changes necessary and updates only those specific nodes in the real browser DOM in a single optimized batch.

Why does React use an in-memory Virtual DOM instead of updating the real DOM directly?

  1. Because modern browsers do not support direct HTML manipulation.
  2. To compare previous and new states (diffing) and batch minimal updates, avoiding expensive browser repaints.
  3. To convert JavaScript code directly into binary web assembly instructions.

Updating the real browser DOM triggers costly layout calculations and repaints. React's Virtual DOM diffs changes in memory first and applies only the exact surgical changes required.

🎯 Exercise 1.1: Declarative Thinking Check

Objective: Analyze the difference between imperative DOM scripting and declarative state-driven rendering.

  1. Write down 3 real-world UI components you use daily (e.g., an Instagram Post, a Spotify Player, an Amazon Cart).
  2. For each component, identify what its State is (e.g., isLiked, isPlaying, itemCount).
  3. Write down how the visual appearance changes when that state flips between true and false. Notice how you only care about the result, not the step-by-step DOM steps!
Lecture 02 · Fundamentals

Vite Setup & Project Anatomy

Learn how modern React applications are bootstrapped using Vite—the lightning-fast build tool powered by native ES modules—and understand every single folder and file in a production-ready React codebase.

What You Will Learn
  • Why modern development uses Vite instead of deprecated Create React App (CRA)
  • Step-by-step commands to scaffold a new React project
  • Anatomy of index.html, main.jsx, App.jsx, and package.json
  • Running the local development server and HMR (Hot Module Replacement)
  • Installing and using React Developer Tools in your browser

01. Why Vite?

For years, beginners started with Create React App (CRA). However, CRA is now deprecated by the React team because its Webpack bundler rebuilt the entire application bundle on every code change, causing slow startup times and sluggish reloads in medium-to-large projects.

Vite (French for "quick", pronounced /veet/) leverages native ES modules (ESM) supported by modern browsers and the ultra-fast Go-based compiler esbuild. Vite provides instant server start, lightning-fast Hot Module Replacement (HMR), and optimized production rollups.

02. Creating Your First React App

Ensure you have Node.js (v18 or higher) installed on your machine. Then open your terminal and run:

Terminal
# 1. Create a fresh React project with Vite
npm create vite@latest my-react-app -- --template react

# 2. Enter the new directory
cd my-react-app

# 3. Install required node dependencies
npm install

# 4. Launch the local dev server
npm run dev

Your terminal will output a local URL (e.g. http://localhost:5173). Open it in your web browser to see your live React app running!

03. Project Anatomy: Breaking Down Every File

Let us explore the created files and their responsibilities:

Folder Structure
my-react-app/
├── node_modules/         # Installed third-party packages
├── public/               # Static assets (favicon, images, logos)
├── src/                  # Where 99% of your React application code lives
│   ├── assets/           # Images/SVGs imported directly into components
│   ├── App.css           # Styles specific to the root App component
│   ├── App.jsx           # Main root application component
│   ├── index.css         # Global baseline CSS styles
│   └── main.jsx          # Application entry point: mounts React to DOM
├── index.html            # The single HTML page loaded by the browser
├── package.json          # Dependencies, scripts, and project metadata
└── vite.config.js        # Vite build and plugin configurations

04. The Mounting Process: index.html to main.jsx

How does React take over the web page? It starts in index.html with a single container div:

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My React App</title>
  </head>
  <body>
    <!-- React will mount inside this empty container -->
    <div id="root"></div>

    <!-- Entry script that boots React -->
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

Next, src/main.jsx grabs that #root element using ReactDOM.createRoot() and renders the top-level <App /> component inside it:

src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
What is React.StrictMode?

<React.StrictMode> is a development-only helper tool that highlights potential bugs, deprecated APIs, and accidental side effects by intentionally executing components twice in local dev. It has zero performance impact on production builds.

Where does the primary React application mount into the browser HTML?

  1. Directly inside the <head> tag of index.html
  2. Inside the package.json manifest script
  3. Inside <div id="root"></div> via ReactDOM.createRoot() in main.jsx

ReactDOM.createRoot selects the HTML element with id root and renders the top-level <App /> tree inside it.

🎯 Exercise 1.2: Environment Setup & Hello World

Objective: Create your first live project and customize the root component.

  1. Open your terminal and run: npm create vite@latest hello-react -- --template react
  2. Run cd hello-react && npm install && npm run dev
  3. Open src/App.jsx in your editor, delete the default boilerplate, and return a clean <h1>Welcome to React Mastery!</h1> with your name and today's date in a paragraph tag.
  4. Save the file and watch Vite instantly hot-reload the browser without full page refresh!
Lecture 03 · Fundamentals

JSX Syntax & Rendering Rules

Master JSX (JavaScript XML)—the expressive syntax extension that lets you write HTML-like markup directly inside JavaScript files. Understand how expressions evaluate, key differences from standard HTML, and essential formatting rules.

What You Will Learn
  • What JSX is and how Babel/Vite transpiles it into React.createElement
  • The curly brace syntax {expression} for dynamic variables, math, and function calls
  • Differences between HTML and JSX: className, htmlFor, camelCase properties
  • The Single Parent Rule and why React Fragments (<></>) prevent extra DOM nodes
  • How booleans, null, and undefined render in JSX

01. What is JSX?

JSX stands for JavaScript XML. It is a syntax extension for JavaScript that allows you to write markup that looks almost identical to HTML directly inside your JS/JSX files.

Browsers cannot understand JSX directly. During your build process, Vite uses Babel or SWC to convert every JSX element into pure JavaScript function calls:

What you write (JSX) vs What the browser executes
// 1. What you write in JSX:
const element = <h1 className="title">Hello React!</h1>;

// 2. What it transpiles to behind the scenes:
const element = React.createElement(
  'h1',
  { className: 'title' },
  'Hello React!'
);

02. Embedding Dynamic JavaScript Expressions

In standard HTML, you write static text. In JSX, you can embed ANY valid JavaScript expression by wrapping it in curly braces {}:

expressions.jsx
function UserProfile() {
  const user = {
    name: 'Sophia Chen',
    role: 'Full-Stack Engineer',
    avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330',
    reputation: 98.4
  };

  const getGreeting = () => 'Welcome back';

  return (
    <div className="profile-card">
      <img src={user.avatar} alt={user.name} width="64" height="64" />
      <h2>{getGreeting()}, {user.name}!</h2>
      <p>Role: <strong>{user.role.toUpperCase()}</strong></p>
      <p>Score: {user.reputation * 10} / 1000</p>
      <p>Next review: {2026 + 1}</p>
    </div>
  );
}

03. Crucial Rules & Differences from HTML

HTML Standard JSX Equivalent Reason
class="card" className="card" class is a reserved keyword in JavaScript for defining classes.
for="email" htmlFor="email" for is a reserved keyword in JavaScript for loops.
onclick="handleClick()" onClick={handleClick} JSX attributes use camelCase and pass function references, not string scripts.
style="color: red; font-size: 16px" style={{ color: 'red', fontSize: '16px' }} Inline styles in JSX must be JavaScript objects with camelCase CSS properties.
Unclosed tags: <img>, <input>, <br> Self-closing: <img />, <input />, <br /> JSX follows strict XML syntax rules. Every single opened tag must be closed.

04. The Single Parent Rule & React Fragments

A React component can only return a single root element. If you try to return two sibling tags side-by-side, transpilation will fail with a syntax error:

invalid-vs-valid.jsx
// ❌ SYNTAX ERROR: Adjacent JSX elements must be wrapped in an enclosing tag
return (
  <h1>Header</h1>
  <p>Description</p>
);

// ✅ SOLUTION: Wrap in a React Fragment (<>...</>) without adding useless divs to the DOM!
return (
  <>
    <h1>Header</h1>
    <p>Description</p>
  </>
);

Which attribute name must you use in JSX when adding CSS classes to an element?

  1. className
  2. class
  3. cssClass
  4. classList

Because class is already a reserved keyword in JavaScript for defining ES6 classes, React uses className to assign HTML class names.

🎯 Exercise 1.3: Convert HTML to Valid JSX

Objective: Fix all errors in an invalid HTML snippet to make it valid JSX.

  1. Given this raw HTML:
    <div class="badge">
      <img src="avatar.png">
      <label for="username" style="font-size: 14px; margin-top: 10px;">Name</label>
      <input type="text" id="username" tabindex="1">
    </div>
  2. Convert class to className.
  3. Convert for to htmlFor.
  4. Self-close the <img> and <input> tags.
  5. Convert the string style into a JavaScript object: style={{ fontSize: '14px', marginTop: '10px' }}.
  6. Convert tabindex to camelCase tabIndex.
Lecture 04 · Fundamentals

Components & Component Architecture

Learn how to design, organize, and compose React components like a senior engineer. Understand functional components, naming conventions, pure rendering principles, and industry file organization strategies.

What You Will Learn
  • How functional components work and why they replaced Class components
  • Why component names MUST start with an uppercase capital letter
  • Composing nested component hierarchies (parent-child relationship)
  • Writing Pure Components that are free from side effects during render
  • Professional project file architectures: feature-based vs type-based

01. Functional Components in Modern React

In modern React, a Component is simply a JavaScript function that accepts input (props) and returns user interface markup (JSX). In the past, developers wrote Class components with render() methods, but since React 16.8 (Hooks), functional components are the official industry standard.

Header.jsx
// Modern Functional Component
export default function Header() {
  return (
    <header className="site-header">
      <div className="logo">CodeTutorium</div>
      <nav>
        <a href="#features">Features</a>
        <a href="#pricing">Pricing</a>
      </nav>
    </header>
  );
}
Critical Rule: Uppercase Naming

React component names MUST start with a capital letter (e.g. Header, UserProfile, ProductCard). If you start a component with a lowercase letter (e.g. function header()), React treats it as an ordinary HTML tag (like <div> or <header>) and will not render your component!

02. Component Composition

Instead of cramming hundreds of lines of HTML into a single monolithic App.jsx, senior developers split interfaces into modular components and compose them together:

App.jsx
import Header from './components/Header';
import HeroSection from './components/HeroSection';
import FeatureList from './components/FeatureList';
import Footer from './components/Footer';

export default function App() {
  return (
    <div className="app-shell">
      <Header />
      <main>
        <HeroSection />
        <FeatureList />
      </main>
      <Footer />
    </div>
  );
}

03. Pure Functions: Keeping Components Idempotent

React relies heavily on the mathematical concept of Pure Functions. A pure function has two characteristics:

  • It minds its own business: It does not mutate any variables or objects that existed before the function call.
  • Same inputs, same outputs: Given the same inputs, it will always return the exact same JSX result.
purity-rules.jsx
// ❌ IMPURE: Mutating external variables during render causes random bugs
let visitorCount = 0;
function BadCounter() {
  visitorCount = visitorCount + 1; // Modifies external state during render!
  return <p>Visitor: {visitorCount}</p>;
}

// ✅ PURE: Keeps rendering logic isolated and predictable
function GoodCounter({ currentCount }) {
  return <p>Visitor: {currentCount}</p>;
}

04. Recommended Directory Organization

Scalable Folder Architecture
src/
├── components/           # Shared generic UI components (Button, Modal, Input)
│   ├── Button/
│   │   ├── Button.jsx
│   │   └── Button.module.css
│   └── Modal/
├── features/             # Domain-specific feature modules
│   ├── auth/             # LoginForm, RegisterModal, authHooks
│   └── products/         # ProductGrid, ProductCard, CartDrawer
├── hooks/                # Reusable custom React hooks
├── utils/                # Helper functions (formatting dates, currency)
├── App.jsx
└── main.jsx

What happens if you name a custom React component starting with a lowercase letter, such as function card()?

  1. The compiler automatically capitalizes it for you.
  2. The app runs without error and styles it in bold.
  3. React mistakes it for a built-in native HTML element and fails to render your custom logic.

React uses the initial letter to distinguish native HTML elements (lowercase like div, span) from custom components (uppercase like Card, Navbar).

🎯 Exercise 1.4: Multi-Component Dashboard Shell

Objective: Create a modular component hierarchy across multiple files.

  1. Create a folder src/components/.
  2. Inside, create StatCard.jsx that renders a title, a numerical value, and a percentage badge.
  3. Create ActivityList.jsx that renders 3 recent activity items.
  4. Import both into App.jsx and render three <StatCard /> elements inside a grid layout above the <ActivityList />.
Lecture 05 · Fundamentals

Props & Unidirectional Data Flow

Master Props (properties)—the mechanism React uses to pass data, configurations, and callbacks from parent components down to child components. Understand destructuring, default fallback values, and the versatile children prop.

What You Will Learn
  • What props are and how they make components dynamic and customizable
  • Unidirectional data flow (one-way data binding from parent to child)
  • Clean object destructuring and default fallback parameters
  • The special children prop for creating reusable layout wrappers
  • Passing functions and callbacks as props to communicate back up

01. What are Props?

Think of React components as custom HTML elements. Just as standard HTML elements accept attributes like href, src, and id, React components accept custom attributes called Props (short for properties).

Props allow a single component template to be reused with completely different data:

CourseCard.jsx
// Child Component receives "props" as its first parameter
export default function CourseCard(props) {
  return (
    <div className="course-card">
      <span className="badge">{props.category}</span>
      <h3>{props.title}</h3>
      <p>Instructor: {props.instructor}</p>
      <p>Duration: {props.durationHours} hours</p>
      <button>Enroll for ${props.price}</button>
    </div>
  );
}

Now the parent component passes the exact values it needs:

CourseCatalog.jsx
import CourseCard from './CourseCard';

export default function CourseCatalog() {
  return (
    <section className="catalog-grid">
      <CourseCard 
        title="React Mastery" 
        instructor="Sarah Connor" 
        category="Frontend" 
        durationHours={18} 
        price={49.99} 
      />
      <CourseCard 
        title="Node.js Backend Architecture" 
        instructor="David Miller" 
        category="Backend" 
        durationHours={24} 
        price={59.99} 
      />
    </section>
  );
}

02. Clean Destructuring & Default Values

Instead of constantly typing props.something, modern JavaScript developers destructure props directly inside the function signature. You can also specify default fallback values:

Button.jsx
// Clean destructuring with default fallback values
export default function Button({ 
  label, 
  variant = 'primary', 
  size = 'medium', 
  disabled = false,
  onClick 
}) {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`} 
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

03. Props are Read-Only (Immutable!)

Golden Rule: Never Mutate Props!

Props in React are strictly read-only. A child component must never modify the props passed to it:

function BadComponent(props) {
  props.title = "New Title"; // ❌ RUNTIME ERROR! Props are frozen/immutable!
}

If a component needs to change data over time in response to user action, that data belongs in State, not props!

04. The Versatile children Prop

Sometimes you want a component to act as a visual container or wrapper (like a Modal dialog, Card, or Layout container) without knowing ahead of time what elements will go inside it. React provides the special children prop for this exact slot pattern:

CardWrapper.jsx
export default function Card({ title, children }) {
  return (
    <div className="card">
      <div className="card-header">
        <h3>{title}</h3>
      </div>
      <div className="card-body">
        {/* Anything nested between <Card>...</Card> renders here! */}
        {children}
      </div>
    </div>
  );
}

// Usage in another component:
<Card title="System Notification">
  <p>Your storage is 85% full. Consider upgrading.</p>
  <button className="btn-upgrade">Upgrade Now</button>
</Card>

Which of the following statements about React props is TRUE?

  1. Child components can directly reassign and update their own props.
  2. Props are immutable (read-only) and flow in one direction from parent to child.
  3. Props can only hold strings, numbers, and booleans; objects and functions are prohibited.

Props flow in one direction (unidirectional data flow) and are read-only. Modifying them violates React's pure rendering architecture.

🎯 Exercise 1.5: Build an Alert Notification Box

Objective: Build a reusable wrapper component using props and children.

  1. Create src/components/Alert.jsx.
  2. It should accept: type (one of: 'success', 'warning', 'danger', default to 'info'), dismissible (boolean, default false), and children.
  3. Render a container with the corresponding CSS class and an icon matching the type.
  4. If dismissible is true, render a close button (×).
  5. In App.jsx, test all 4 alert types with different custom paragraph text inside!
Lecture 06 · State & Interactivity

State Management with the useState Hook

Discover the heart of dynamic React applications: component State. Learn why local JavaScript variables fail to re-render the screen, master the useState hook, understand asynchronous state batching, and handle object/array states properly.

What You Will Learn
  • The difference between Props (external/immutable) and State (internal/mutable)
  • The useState hook syntax and array destructuring pattern
  • Why changing regular variables does not trigger a re-render
  • The functional updater pattern setState(prev => prev + 1) to avoid stale state
  • Safely updating arrays and objects in state using the spread operator (...)

01. Why Variables Don't Work for Interactive UIs

Suppose you want a simple counter button that increments when clicked. If you try using a regular local variable:

broken-counter.jsx
export default function BrokenCounter() {
  let count = 0;

  function handleClick() {
    count = count + 1;
    console.log('Current count:', count); // Logs 1, 2, 3 in the console...
  }

  // ❌ The screen NEVER updates! It stays stuck at 0!
  return <button onClick={handleClick}>Clicked {count} times</button>;
}

Why did this fail? Two critical reasons:

  1. Local variables don't persist across renders: Whenever React re-executes a component function, local variables are reinitialized from scratch.
  2. Changing local variables does not notify React: React has no magical listener watching local variables. To trigger a re-render, you must inform React through its official state API.

02. Enter the useState Hook

React provides the useState Hook to store data that persists across renders and triggers an automated UI update when changed:

WorkingCounter.jsx
import { useState } from 'react';

export default function WorkingCounter() {
  // Syntax: const [stateValue, setterFunction] = useState(initialValue);
  const [count, setCount] = useState(0);

  function handleIncrement() {
    setCount(count + 1); // Updates state and schedules a re-render!
  }

  return (
    <div className="counter-box">
      <h2>Score: {count}</h2>
      <button onClick={handleIncrement}>Add +1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

03. The Functional Updater Pattern

React batches multiple state updates together for performance. If you need to calculate the next state based on the previous state, always pass an updater callback function rather than relying on the raw state variable:

stale-vs-functional.jsx
// ❌ BUGGY: If called 3 times in a row, it only increments once!
function incrementThreeTimesBroken() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}

// ✅ SAFE & PROFESSIONAL: Uses previous state snapshot guarantee
function incrementThreeTimesSafe() {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1); // Correctly increments by +3!
}

04. Updating Objects & Arrays in State

Remember: React detects changes by comparing object memory references (shallow equality). If you mutate an existing array or object directly in place, its memory reference stays identical, and React will skip re-rendering!

immutability-recipes.jsx
const [user, setUser] = useState({ name: 'Alex', age: 25, city: 'London' });
const [items, setItems] = useState(['React', 'Vite']);

// 1. Updating an Object (Spread existing properties, overwrite target):
setUser(prev => ({
  ...prev,
  city: 'Tokyo'
}));

// 2. Adding to an Array (DO NOT use items.push!):
setItems(prev => [...prev, 'TypeScript']);

// 3. Removing an item from an Array (use .filter()):
setItems(prev => prev.filter(item => item !== 'Vite'));

How should you add a new item to an array stored in React state?

  1. items.push(newItem)
  2. items[items.length] = newItem
  3. setItems(prev => [...prev, newItem])

Direct mutation via .push() does not create a new array reference, meaning React will not detect the change. Spreading into a new array ensures state immutability.

🎯 Exercise 2.1: Multi-Step Quantity & Price Calculator

Objective: Build a component with multiple interdependent state updates.

  1. Create a component QuantitySelector.jsx with a state quantity initialized to 1.
  2. Include buttons for: - 1 (cannot go below 1), + 1, and + 10.
  3. Display the total price based on a unit price prop (e.g. unitPrice = 24.99).
  4. Add a discount state: if quantity >= 5, apply a 15% discount and display a green "15% Bulk Discount Applied!" badge.
Lecture 07 · State & Interactivity

Handling Events & Synthetic Events

Master user event handling in React. Understand React's SyntheticEvent wrapper, passing custom arguments to handlers, stopping propagation, preventing default browser submissions, and avoiding the common infinite loop trap.

What You Will Learn
  • The SyntheticEvent cross-browser normalization wrapper
  • Attaching click, change, submit, and keyboard event handlers
  • Passing custom arguments to event handlers cleanly
  • Preventing default form submissions with e.preventDefault()
  • The critical difference between passing a function reference vs calling it immediately

01. React SyntheticEvents

When you attach an event handler in React (like onClick or onKeyDown), React does not attach raw event listeners directly to the individual DOM nodes. Instead, it wraps native browser events in an object called a SyntheticEvent.

The SyntheticEvent provides a completely identical interface across all browsers (Chrome, Safari, Firefox, Edge, mobile browsers), eliminating cross-browser inconsistencies with event properties.

02. Passing Functions: Reference vs Immediate Invocation

The #1 Beginner Trap: Immediate Invocation

You must always pass a function reference to an event attribute, NOT the result of calling the function immediately:

// ❌ WRONG: Calls handleClick() immediately during rendering!
// This triggers an infinite re-render loop if handleClick calls setState!
<button onClick={handleClick()}>Click Me</button>

// ✅ CORRECT: Passes the function as a reference to be called upon user click
<button onClick={handleClick}>Click Me</button>

// ✅ CORRECT: Using an inline arrow function when you need to pass custom arguments
<button onClick={() => handleDelete(product.id)}>Delete Product</button>

03. Common Event Handlers in Practice

EventShowcase.jsx
import { useState } from 'react';

export default function EventShowcase() {
  const [text, setText] = useState('');
  const [isHovered, setIsHovered] = useState(false);

  // Input Change Event
  function handleInputChange(e) {
    setText(e.target.value);
  }

  // Key Down Event (e.g. detecting Enter key)
  function handleKeyDown(e) {
    if (e.key === 'Enter') {
      alert(`Submitted: ${text}`);
    }
  }

  return (
    <div className="events-container">
      <input 
        type="text" 
        placeholder="Type and press Enter..." 
        value={text} 
        onChange={handleInputChange} 
        onKeyDown={handleKeyDown} 
      />

      <div 
        className={`hover-box ${isHovered ? 'active' : ''}`}
        onMouseEnter={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}
      >
        {isHovered ? 'Mouse is inside!' : 'Hover over me'}
      </div>
    </div>
  );
}

04. Stopping Event Propagation & Preventing Defaults

In standard web forms, clicking a submit button reloads the entire page. In Single Page Applications (SPAs), we prevent this using e.preventDefault():

FormSubmission.jsx
function SearchForm() {
  function handleSearchSubmit(e) {
    e.preventDefault(); // Stops the browser from reloading the page!
    console.log('Performing smooth AJAX client search...');
  }

  return (
    <form onSubmit={handleSearchSubmit}>
      <input type="search" placeholder="Search docs..." />
      <button type="submit">Search</button>
    </form>
  );
}

What is the result of writing <button onClick={alert('Hello!')}>?

  1. The alert triggers when the user clicks the button.
  2. The alert executes immediately during component rendering before any user interaction.
  3. React throws a compile-time syntax error.

Adding parentheses () calls the function immediately during the rendering phase. You should write onClick={() => alert('Hello!')} to defer execution until the click occurs.

🎯 Exercise 2.2: Interactive Live Filter Keypress Box

Objective: Handle input, blur, focus, and keyboard events.

  1. Create a component with a search input and a character counter (e.g. "Characters: 14 / 50").
  2. When the input is focused, change the border color to cyan. When blurred, revert it.
  3. If the user presses the Escape key inside the input, immediately clear the text state.
  4. Add a "Copy to Clipboard" button that uses the browser Clipboard API when clicked.
Lecture 08 · State & Interactivity

Conditional Rendering Patterns

Learn the 4 core design patterns for conditionally showing, hiding, and switching user interface elements in React. Master ternary operators, short-circuit logic, guard clauses, and avoid the infamous "0 rendered on screen" bug.

What You Will Learn
  • Pattern 1: The Ternary Operator for binary branching (if/else)
  • Pattern 2: Logical AND (&&) for showing or hiding single elements
  • The infamous 0 && <Component /> falsy number bug and how to prevent it
  • Pattern 3: Early returns (Guard Clauses) for loading and error states
  • Pattern 4: Object map lookup for multi-status states (Tabs, Steps, Enums)

01. Pattern 1: The Ternary Operator

When you have two distinct mutually exclusive UI states (like Logged In vs Logged Out), the JavaScript ternary operator condition ? <TrueComponent /> : <FalseComponent /> is the cleanest inline solution:

AuthBanner.jsx
export default function AuthBanner({ isLoggedIn, username }) {
  return (
    <div className="auth-bar">
      {isLoggedIn ? (
        <div className="welcome">
          <span>Hello, <strong>{username}</strong>!</span>
          <button className="btn-logout">Log Out</button>
        </div>
      ) : (
        <div className="guest">
          <span>Welcome, Guest. Please sign in.</span>
          <button className="btn-login">Sign In</button>
        </div>
      )}
    </div>
  );
}

02. Pattern 2: Short-Circuit Evaluation (&&)

When you only want to render something when a condition is true, and render NOTHING when it is false, use the logical && operator:

NotificationBadge.jsx
function Inbox({ unreadMessages, hasWarning }) {
  return (
    <div className="inbox">
      <h2>Messages</h2>
      
      {/* Only renders the red badge if unreadMessages is greater than 0 */}
      {unreadMessages > 0 && (
        <span className="badge-danger">{unreadMessages} new</span>
      )}

      {hasWarning && <p className="warning-text">Your inbox is almost full!</p>}
    </div>
  );
}
Beware the "0" Falsy Bug!

In JavaScript, 0 && <Component /> evaluates to 0 (not false). React renders the number 0 directly onto your page!

// ❌ BUGGY: If count is 0, this renders a stray literal "0" onto the screen!
{count && <p>You have items</p>}

// ✅ SAFE: Always compare explicitly to a boolean or use greater-than
{count > 0 && <p>You have items</p>}
{Boolean(count) && <p>You have items</p>}

03. Pattern 3: Early Return (Guard Clauses)

If an entire component should not render or needs to display a full-page loading skeleton or error state, return early before the main JSX:

DataView.jsx
export default function DataView({ isLoading, error, data }) {
  // 1. Guard Clause for Loading
  if (isLoading) {
    return <div className="spinner">Loading your profile...</div>;
  }

  // 2. Guard Clause for Error
  if (error) {
    return <div className="error-box">Failed to load: {error.message}</div>;
  }

  // 3. Main Happy Path Render
  return (
    <article className="content">
      <h1>{data.title}</h1>
      <p>{data.body}</p>
    </article>
  );
}

What will be rendered by the code {items.length && <ItemList />} when items is an empty array ([])?

  1. Nothing will be rendered.
  2. A literal number 0 will appear in the HTML.
  3. An error: "Cannot read property of empty array".

Since items.length is 0, JavaScript's logical && short-circuits and returns 0. React considers 0 a valid renderable number, printing a stray "0" on the screen.

🎯 Exercise 2.3: Multi-State Tab Switcher

Objective: Render different component views based on active tab state.

  1. Create a component with 3 tabs: Overview, Settings, and Billing.
  2. Store the active tab in state (e.g. const [tab, setTab] = useState('overview')).
  3. Highlight the active tab button with a CSS class active.
  4. Use conditional rendering to show the corresponding content panel when clicked.
Lecture 09 · State & Interactivity

Rendering Lists & The Critical Key Prop

Master transforming raw JavaScript arrays of data into dynamic lists of React elements using the map() method. Understand why the unique key prop is mandatory for performance, and why using array indexes as keys leads to insidious UI bugs.

What You Will Learn
  • Using array.map() to transform data collections into JSX elements
  • What the key prop is and how React's reconciliation algorithm uses it
  • Why using array index as a key breaks state when deleting or sorting items
  • Filtering and searching arrays dynamically before mapping
  • Handling empty state fallbacks when an array contains 0 items

01. Mapping Arrays to Elements

In React, we do not use for loops inside JSX markup. Instead, we use JavaScript's functional .map() method to project an array of data into an array of JSX elements:

UserDirectory.jsx
export default function UserDirectory() {
  const developers = [
    { id: 'dev-101', name: 'Sarah Connor', skill: 'React & Next.js' },
    { id: 'dev-102', name: 'Kenji Sato', skill: 'TypeScript & Rust' },
    { id: 'dev-103', name: 'Amara Okafor', skill: 'UI/UX & CSS' }
  ];

  return (
    <div className="directory">
      <h2>Engineering Team</h2>
      <ul>
        {developers.map(dev => (
          <li key={dev.id} className="dev-card">
            <strong>{dev.name}</strong> — <span>{dev.skill}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

02. Why React Demands the key Prop

Notice the key={dev.id} attribute on the <li> element above. If you omit it, React will print a red warning in your browser console: "Each child in a list should have a unique 'key' prop."

Why is this key so critical? During reconciliation, when an item is added, moved, sorted, or removed from a list, React compares the new list with the previous list. Without a stable key, React cannot tell which exact item was modified versus which items just shifted position.

03. The Danger of Using Array Index as a Key

Why Index as Key Causes Data Corruption

Beginners often write items.map((item, index) => <li key={index}>...</li>). This is dangerous if your list is dynamic!

If you delete the first item at index 0, the second item now becomes index 0, the third becomes index 1, and so on. Any internal input values, checkbox checks, or animations attached to index 0 will now mistakenly stay bound to the wrong item!

Rule: Always use unique, persistent IDs (e.g. database UUIDs, item.id, or crypto.randomUUID() when creating items).

04. Filtering Lists with Empty State Handling

FilterableList.jsx
import { useState } from 'react';

export default function SearchableProductList({ products }) {
  const [query, setQuery] = useState('');

  // Filter products based on search input
  const filteredProducts = products.filter(product =>
    product.name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <div>
      <input 
        type="search" 
        placeholder="Search products..." 
        value={query}
        onChange={e => setQuery(e.target.value)}
      />

      {filteredProducts.length === 0 ? (
        <p className="empty-msg">No products match your search.</p>
      ) : (
        <div className="grid">
          {filteredProducts.map(p => (
            <div key={p.id} className="item">
              <h4>{p.name}</h4>
              <p>${p.price}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

When is it acceptable to use the array index as a key in React?

  1. Always, it is the recommended best practice.
  2. Only when the list has more than 1,000 items.
  3. Only when the list is purely static (never filtered, sorted, added to, or deleted) and items have no natural unique IDs.

If the list is strictly read-only and static, index can be used without bugs. If items can be reordered, deleted, or inserted, index keys cause subtle rendering and state bugs.

🎯 Exercise 2.4: Interactive Inventory Manager

Objective: Render dynamic lists with add and delete capabilities.

  1. Initialize state with an array of 3 inventory items: { id: '1', name: 'Mechanical Keyboard', qty: 12 }.
  2. Render each item inside a table with columns: ID, Name, Quantity, and an "Actions" column.
  3. Include a "Delete" button beside each item that filters it out by its unique ID.
  4. Include a simple form at the top with a text input and number input to add a new product with crypto.randomUUID() as its ID.
Lecture 10 · State & Interactivity

Forms & Controlled Components

Master managing user input in React. Understand the Controlled Component pattern where React state acts as the Single Source of Truth, handle text fields, selects, checkboxes, and manage multi-input forms with a single state object.

What You Will Learn
  • Controlled vs. Uncontrolled input elements
  • The two-way binding pattern: value={state} and onChange={handler}
  • Managing multi-field forms using dynamic computed property names ([name]: value)
  • Handling checkboxes, radio groups, and dropdown selects
  • Form validation, submission handling, and error states

01. Controlled vs. Uncontrolled Components

In standard HTML, form elements like <input> and <textarea> maintain their own internal state based on what the user types. This is known in React as an Uncontrolled Component.

In modern React, we prefer Controlled Components: the input's value is driven directly by React state, and changes are piped through state setters. React state is the Single Source of Truth.

ControlledInput.jsx
import { useState } from 'react';

export default function SimpleNameInput() {
  const [name, setName] = useState('');

  return (
    <div>
      <label htmlFor="full-name">Your Name:</label>
      <input 
        id="full-name"
        type="text" 
        value={name} // 1. Push state into input value
        onChange={e => setName(e.target.value)} // 2. Push input edits back into state
      />
      <p>Preview: {name ? name : 'Anonymous'}</p>
    </div>
  );
}

02. Managing Multi-Field Forms with a Single State Object

What if your form has 8 input fields? Creating 8 individual useState calls leads to repetitive boilerplate. Instead, use a single state object with dynamic object keys matching each input's name attribute:

RegistrationForm.jsx
import { useState } from 'react';

export default function RegistrationForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    role: 'student',
    agreeTerms: false
  });

  const [error, setError] = useState('');

  // Single generic change handler for all inputs
  function handleChange(e) {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      // If it's a checkbox, store its boolean checked state, otherwise store value
      [name]: type === 'checkbox' ? checked : value
    }));
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (!formData.agreeTerms) {
      setError('You must agree to the terms to continue.');
      return;
    }
    setError('');
    console.log('Submitted registration data:', formData);
  }

  return (
    <form onSubmit={handleSubmit} className="form-card">
      <h3>Create Free Account</h3>
      {error && <div className="error-alert">{error}</div>}

      <div className="field">
        <label>Email Address</label>
        <input 
          type="email" 
          name="email" 
          value={formData.email} 
          onChange={handleChange} 
          required 
        />
      </div>

      <div className="field">
        <label>Password</label>
        <input 
          type="password" 
          name="password" 
          value={formData.password} 
          onChange={handleChange} 
          required 
        />
      </div>

      <div className="field">
        <label>Learning Track</label>
        <select name="role" value={formData.role} onChange={handleChange}>
          <option value="student">Student / Beginner</option>
          <option value="developer">Professional Developer</option>
          <option value="educator">Teacher / Educator</option>
        </select>
      </div>

      <div className="checkbox-field">
        <input 
          type="checkbox" 
          name="agreeTerms" 
          id="terms" 
          checked={formData.agreeTerms} 
          onChange={handleChange} 
        />
        <label htmlFor="terms">I agree to terms of service</label>
      </div>

      <button type="submit" className="btn-primary">Sign Up</button>
    </form>
  );
}

What defines a "Controlled Component" in React?

  1. A component that uses strict TypeScript validation.
  2. An input element whose current value is driven by React state via the value and onChange props.
  3. A component that cannot be unmounted by its parent.

A controlled component takes its current value from React state and calls an onChange handler when user changes occur, keeping React as the single source of truth.

🎯 Exercise 2.5: Contact Form with Live Validation

Objective: Build a contact form with client-side feedback.

  1. Fields: Name, Email, Subject dropdown, Message textarea.
  2. Real-time validation: if message is under 20 characters, display a warning counter: "Message too short (X / 20 chars)".
  3. Disable the submit button until all fields are valid.
  4. Upon submission, display a pleasant success banner and reset all fields back to blank.
Lecture 11 · Practical Project 1

🛠️ Project 1: Dynamic Task & Habit Tracker

Put everything learned in Units I and II into action! In this real-world hands-on project, you will build a complete, interactive Task & Habit Tracker featuring item creation, completion toggling, deletion, filtering, and real-time completion statistics.

Project Features & Architecture
  • State Management: Array of tasks with IDs, titles, categories, and completed flags
  • Form Handling: Controlled inputs to add new tasks with validation
  • Filters: Live switching between "All", "Pending", and "Completed"
  • Progress Bar: Dynamic calculation of completed task percentage
  • Empty States: Friendly empty state banners when no tasks exist

01. Complete Project Source Code

Create a file named src/HabitTracker.jsx and study how the state, events, and subcomponents work harmoniously:

src/HabitTracker.jsx
import { useState } from 'react';

export default function HabitTracker() {
  // 1. Main task list state
  const [tasks, setTasks] = useState([
    { id: 't-1', text: 'Complete React Lecture 10', category: 'Study', completed: true },
    { id: 't-2', text: 'Drink 2L of water', category: 'Health', completed: false },
    { id: 't-3', text: 'Push Git commits to GitHub', category: 'Code', completed: false }
  ]);

  // 2. Input states
  const [inputText, setInputText] = useState('');
  const [category, setCategory] = useState('Study');
  const [filter, setFilter] = useState('all'); // 'all' | 'pending' | 'completed'

  // 3. Add new task
  function handleAddTask(e) {
    e.preventDefault();
    if (!inputText.trim()) return;

    const newTask = {
      id: crypto.randomUUID(),
      text: inputText.trim(),
      category: category,
      completed: false
    };

    setTasks(prev => [newTask, ...prev]);
    setInputText('');
  }

  // 4. Toggle completed flag
  function handleToggle(id) {
    setTasks(prev => prev.map(task => 
      task.id === id ? { ...task, completed: !task.completed } : task
    ));
  }

  // 5. Delete task
  function handleDelete(id) {
    setTasks(prev => prev.filter(task => task.id !== id));
  }

  // 6. Filter tasks
  const filteredTasks = tasks.filter(task => {
    if (filter === 'pending') return !task.completed;
    if (filter === 'completed') return task.completed;
    return true;
  });

  // 7. Calculate statistics
  const totalCount = tasks.length;
  const completedCount = tasks.filter(t => t.completed).length;
  const percentage = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;

  return (
    <div className="tracker-app">
      <header className="tracker-header">
        <h1>Daily Habit & Task Tracker</h1>
        <p>Stay consistent. Ship projects.</p>

        <div className="progress-card">
          <div className="progress-stats">
            <span>Progress: {completedCount} of {totalCount} completed</span>
            <strong>{percentage}%</strong>
          </div>
          <div className="progress-track">
            <div className="progress-fill" style={{ width: `${percentage}%` }}></div>
          </div>
        </div>
      </header>

      {/* Add Task Form */}
      <form onSubmit={handleAddTask} className="tracker-form">
        <input 
          type="text" 
          placeholder="What habit do you want to achieve today?" 
          value={inputText} 
          onChange={e => setInputText(e.target.value)} 
          required 
        />
        <select value={category} onChange={e => setCategory(e.target.value)}>
          <option value="Study">Study</option>
          <option value="Code">Code</option>
          <option value="Health">Health</option>
          <option value="Work">Work</option>
        </select>
        <button type="submit">+ Add Habit</button>
      </form>

      {/* Filter Tabs */}
      <div className="filter-tabs">
        <button 
          className={filter === 'all' ? 'active' : ''} 
          onClick={() => setFilter('all')}
        >All ({totalCount})</button>
        <button 
          className={filter === 'pending' ? 'active' : ''} 
          onClick={() => setFilter('pending')}
        >Active ({totalCount - completedCount})</button>
        <button 
          className={filter === 'completed' ? 'active' : ''} 
          onClick={() => setFilter('completed')}
        >Done ({completedCount})</button>
      </div>

      {/* Task List */}
      <ul className="task-list">
        {filteredTasks.length === 0 ? (
          <li className="empty-state">No tasks in this view. Keep up the great work!</li>
        ) : (
          filteredTasks.map(task => (
            <li key={task.id} className={`task-item ${task.completed ? 'done' : ''}`}>
              <label className="checkbox-wrap">
                <input 
                  type="checkbox" 
                  checked={task.completed} 
                  onChange={() => handleToggle(task.id)} 
                />
                <span className="task-label">{task.text}</span>
              </label>
              <span className=`category-badge cat-${task.category.toLowerCase()}`>
                {task.category}
              </span>
              <button 
                onClick={() => handleDelete(task.id)} 
                className="btn-del" 
                title="Delete task"
              >
                ×
              </button>
            </li>
          ))
        )}
      </ul>
    </div>
  );
}
🚀 Project 1 Challenge Missions

Take this project further by adding these 3 advanced features:

  1. Priority Flag: Add an option to mark tasks as "High Priority" and display a glowing red flame icon (🔥).
  2. Clear All Completed: Add a button at the bottom to bulk-delete all completed items with a single click.
  3. Edit Task Text: Add a double-click event on task labels that turns them into editable inline inputs!
Lecture 12 · Lifecycle & Effects

The useEffect Hook & Lifecycle Management

Master handling side effects in React. Understand the component lifecycle (Mount, Update, Unmount), master the Dependency Array rules, and write cleanup functions to avoid memory leaks with timers and event listeners.

What You Will Learn
  • What "Side Effects" are in functional programming and React
  • The 3 phases of a component: Mounting, Updating, and Unmounting
  • The Dependency Array: Empty [], Dependencies [a, b], and No Array
  • The Cleanup Function: Stopping intervals, unsubscribing, preventing memory leaks
  • How StrictMode interacts with useEffect in development

01. What is a Side Effect?

A pure React component should only receive props and return JSX. However, real-world applications need to interact with the outside world beyond React's rendering pipeline. These operations are called Side Effects:

  • Fetching data from an external REST API
  • Setting timers (setInterval, setTimeout)
  • Directly changing the document title (document.title = '...')
  • Listening to global window events (window resize, scroll)
  • Reading or writing to browser localStorage

02. The 3 Dependency Array Modes

The useEffect hook takes two arguments: an effect callback function, and an optional Dependency Array. How and when it executes depends entirely on this array:

useEffectModes.jsx
import { useEffect, useState } from 'react';

// Case 1: No Dependency Array (DANGEROUS: Runs after EVERY single render!)
useEffect(() => {
  console.log('Runs after initial mount AND every state/prop update!');
});

// Case 2: Empty Dependency Array [] (Runs ONCE on initial Mount only)
useEffect(() => {
  console.log('Runs exactly ONCE when the component first appears on screen.');
}, []);

// Case 3: With Dependencies [userId] (Runs on Mount + whenever userId changes)
useEffect(() => {
  console.log(`Fetching new data because userId changed to: ${userId}`);
}, [userId]);

03. The Cleanup Function

If your effect creates an ongoing background process (like a recurring setInterval or a window addEventListener), it will continue running in memory even after the user navigates away from that page, causing severe performance memory leaks!

To prevent this, return a cleanup function from your effect:

WindowResizeListener.jsx
import { useState, useEffect } from 'react';

export default function WindowWidthTracker() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setWindowWidth(window.innerWidth);
    }

    // 1. Set up the event listener on Mount
    window.addEventListener('resize', handleResize);
    console.log('Resize listener attached.');

    // 2. Return cleanup function (Runs on Unmount or before re-running effect)
    return () => {
      window.removeEventListener('resize', handleResize);
      console.log('Listener cleaned up! No memory leaks.');
    };
  }, []); // Empty array ensures listener is only attached once

  return <p>Current Screen Width: <strong>{windowWidth}px</strong></p>;
}

When does the cleanup function returned from a useEffect execute?

  1. Only when the browser window closes.
  2. When the component unmounts, and immediately before re-running the effect on dependency change.
  3. Before the component renders for the first time.

The cleanup function runs when the component is unmounted from the DOM and before the effect runs again due to dependency updates, cleaning up lingering subscriptions or timers.

🎯 Exercise 3.1: Live Digital Clock & Document Title Sync

Objective: Practice timers and cleanup in useEffect.

  1. Create a component DigitalClock.jsx.
  2. Use useState to store the current time string (new Date().toLocaleTimeString()).
  3. Use useEffect with setInterval to update the time state every 1,000 milliseconds.
  4. Return a cleanup function with clearInterval().
  5. Also update document.title = `Clock: ${time}` inside an effect so the browser tab displays the live time!
Lecture 13 · Lifecycle & Effects

Data Fetching & REST APIs

Connect your React frontend to real-world cloud APIs. Learn how to fetch data asynchronously inside useEffect, manage the 3 fundamental states of every network request (Loading, Error, Data), and cancel in-flight requests with AbortController.

What You Will Learn
  • How to perform asynchronous HTTP requests with fetch and async/await
  • The 3 UI States: isLoading, error, and data
  • Preventing race conditions and unmounted state updates with AbortController
  • Triggering refetches with dependency parameters (e.g. pagination, search queries)
  • Handling HTTP error codes (404, 500) that fetch() does not reject automatically

01. The Standard Data Fetching Pattern

Because the callback function passed to useEffect cannot be declared as async directly (it must return either nothing or a cleanup function), we declare an async function inside the effect and call it immediately:

UserListFetch.jsx
import { useState, useEffect } from 'react';

export default function UserList() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // AbortController allows canceling request if component unmounts mid-flight
    const controller = new AbortController();

    async function fetchUsers() {
      try {
        setIsLoading(true);
        setError(null);

        const response = await fetch('https://jsonplaceholder.typicode.com/users', {
          signal: controller.signal
        });

        if (!response.ok) {
          throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
        }

        const data = await response.json();
        setUsers(data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setIsLoading(false);
      }
    }

    fetchUsers();

    return () => controller.abort(); // Cleanup: abort network request on unmount
  }, []);

  // 1. Loading State
  if (isLoading) return <div className="loading-spinner">Fetching developers...</div>;

  // 2. Error State
  if (error) return <div className="error-alert">Error loading data: {error}</div>;

  // 3. Success State
  return (
    <div className="users-container">
      <h2>Active Community Members ({users.length})</h2>
      <div className="user-grid">
        {users.map(user => (
          <div key={user.id} className="user-card">
            <h3>{user.name}</h3>
            <p>📧 {user.email}</p>
            <p>🏢 {user.company.name}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

02. Triggering API Calls on User Search / Page Change

To refetch data whenever a query parameter or page number changes, simply add that variable to the dependency array:

PaginatedPostViewer.jsx
const [postId, setPostId] = useState(1);
const [post, setPost] = useState(null);

useEffect(() => {
  fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
    .then(res => res.json())
    .then(data => setPost(data));
}, [postId]); // Automatically re-runs whenever postId updates!

Why can you NOT write useEffect(async () => { ... }, []) directly?

  1. Because async/await is unsupported in modern React.
  2. Because useEffect can only fetch local text files, not remote endpoints.
  3. Because async functions return a Promise, but useEffect expects either nothing or a synchronous cleanup function.

An async function implicitly returns a Promise. React expects the effect callback to return a cleanup function or undefined, which is why you declare the async function inside the effect body.

🎯 Exercise 3.2: Live GitHub Profile Explorer

Objective: Fetch dynamic data from the public GitHub API.

  1. Create an input where a user types any GitHub username (default: "facebook").
  2. Include a "Search" button that triggers a fetch to https://api.github.com/users/{username}.
  3. Display the user's avatar image, name, bio, public repo count, and follower count.
  4. Properly display an error message if the user does not exist (HTTP 404).
Lecture 14 · Advanced Hooks

DOM Refs with useRef & Mutable Values

Learn how to access real DOM elements and manage mutable values that persist across renders without triggering a re-render. Understand the difference between useState and useRef, auto-focusing inputs, and tracking intervals.

What You Will Learn
  • What useRef is and the { current: initialValue } reference container
  • Directly accessing and manipulating DOM nodes (focus, scroll, measurements)
  • Storing mutable values (timer IDs, click counts) without triggering expensive re-renders
  • Comparing useState vs useRef vs plain variables
  • When to use refs vs when to stick to standard declarative React state

01. Accessing the DOM: Auto-focusing an Input

While React encourages you to declare your UI declaratively, sometimes you need imperative access to a raw DOM element (e.g. focusing an input field, triggering audio/video playback, or measuring canvas dimensions):

AutoSearch.jsx
import { useRef, useEffect } from 'react';

export default function AutoSearch() {
  // 1. Create a ref container
  const searchInputRef = useRef(null);

  useEffect(() => {
    // 2. Once mounted, searchInputRef.current points directly to the real <input> DOM node!
    searchInputRef.current.focus();
  }, []);

  return (
    <div>
      <input 
        ref={searchInputRef} 
        type="search" 
        placeholder="Cursor starts focused here automatically..." 
      />
    </div>
  );
}

02. Storing Mutable Values Without Re-rendering

Here is the second superpower of useRef: it acts as an instance variable container. If you update ref.current = newValue, the value is preserved across all renders, but it does NOT trigger a re-render.

This is the perfect pattern for storing timer IDs, audio controllers, or tracking render counts:

Stopwatch.jsx
import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  // Store timer interval ID in a ref so mutating it doesn't cause extra re-renders
  const timerIdRef = useRef(null);

  function handleStart() {
    if (isRunning) return;
    setIsRunning(true);
    timerIdRef.current = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
  }

  function handleStop() {
    clearInterval(timerIdRef.current);
    timerIdRef.current = null;
    setIsRunning(false);
  }

  function handleReset() {
    handleStop();
    setSeconds(0);
  }

  return (
    <div className="stopwatch-box">
      <h1>{seconds}s</h1>
      <button onClick={handleStart} disabled={isRunning}>Start</button>
      <button onClick={handleStop} disabled={!isRunning}>Pause</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
}

What happens when you update a ref via myRef.current = 42?

  1. The component immediately re-renders with the new value.
  2. The value changes silently in memory without triggering a re-render.
  3. React throws an error because refs are read-only.

Updating useRef.current is purely synchronous and does NOT trigger a re-render. It is intended for values that do not directly affect the visual output of the component.

🎯 Exercise 3.3: Component Render Counter

Objective: Track how many times a component renders without causing an infinite loop.

  1. Create a component with an input text box controlled by useState.
  2. Create a ref const renderCount = useRef(0).
  3. In a useEffect with no dependency array (runs on every render), increment renderCount.current += 1.
  4. Display: "This component has re-rendered: X times" as you type in the input.
Lecture 15 · Advanced Hooks

Building Custom React Hooks

Elevate your code quality from junior to senior by extracting and sharing stateful logic. Learn the official Rules of Hooks and create custom hooks for localStorage synchronization, API fetching, and debounced search.

What You Will Learn
  • Why custom hooks are the ultimate tool for DRY (Don't Repeat Yourself) logic
  • The 2 golden Rules of React Hooks
  • Building useLocalStorage to persist state in the browser automatically
  • Building useFetch for clean one-line data fetching with loading/error flags
  • Building useDebounce to throttle rapid search keystrokes

01. What is a Custom Hook?

A custom hook is simply a regular JavaScript function whose name starts with "use" (like useLocalStorage, useAuth, or useWindowSize) and that calls other React hooks (like useState, useEffect, useRef) inside itself.

The Two Golden Rules of Hooks
  1. Only call Hooks at the top level: Never call hooks inside loops, conditional if statements, or nested functions. React relies on the exact execution order of hooks across renders.
  2. Only call Hooks from React functions: Only call hooks from React functional components or other custom hooks. Never call them from ordinary helper scripts.

02. Building useLocalStorage

Have you ever wanted state to automatically persist in localStorage so it survives page reloads? Let's build a custom hook that behaves just like useState, but syncs to browser storage:

src/hooks/useLocalStorage.js
import { useState, useEffect } from 'react';

export function useLocalStorage(key, fallbackValue) {
  // Initialize state from localStorage or fallback
  const [value, setValue] = useState(() => {
    try {
      const saved = localStorage.getItem(key);
      return saved !== null ? JSON.parse(saved) : fallbackValue;
    } catch (e) {
      console.error('Failed to read localStorage:', e);
      return fallbackValue;
    }
  });

  // Automatically sync changes back into localStorage
  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (e) {
      console.error('Failed to write localStorage:', e);
    }
  }, [key, value]);

  return [value, setValue];
}

Now, any component in your entire project can persist state with a single line of code:

ThemeToggle.jsx
import { useLocalStorage } from './hooks/useLocalStorage';

export default function ThemeToggle() {
  // Persists user preference across browser restarts automatically!
  const [theme, setTheme] = useLocalStorage('user-theme', 'dark');

  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      Current Theme: {theme} (Click to toggle)
    </button>
  );
}

03. Building useFetch

src/hooks/useFetch.js
import { useState, useEffect } from 'react';

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;
    setLoading(true);

    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error(`HTTP Error ${res.status}`);
        return res.json();
      })
      .then(result => {
        if (isMounted) setData(result);
      })
      .catch(err => {
        if (isMounted) setError(err.message);
      })
      .finally(() => {
        if (isMounted) setLoading(false);
      });

    return () => { isMounted = false; };
  }, [url]);

  return { data, loading, error };
}

What naming convention is required for React to recognize a function as a custom hook and enforce hook rules?

  1. The function name must begin with the lowercase prefix use (e.g. useAuth).
  2. The function name must end with Hook (e.g. authHook).
  3. The function must be decorated with @hook.

React's ESLint plugin and internals rely on the use prefix convention (e.g., useCounter) to automatically enforce the Rules of Hooks.

🎯 Exercise 3.4: Build a useToggle Custom Hook

Objective: Abstract boolean state toggling into a reusable utility.

  1. Create src/hooks/useToggle.js that accepts an optional initialValue = false.
  2. Return an array: [value, toggle, setTrue, setFalse].
  3. Use this hook in an FAQ Accordion component to expand/collapse questions cleanly.
Lecture 16 · Practical Project 2

🛠️ Project 2: Live Weather & Crypto Dashboard

Build an interactive Live Dashboard application consuming real REST APIs, utilizing your custom hooks, managing search queries, handling loading/error indicators, and storing favorite tokens in localStorage.

Project Features
  • Live API Data: Fetch real-time cryptocurrency prices from CoinGecko public API
  • Custom Hook Integration: Use useLocalStorage to remember pinned favorites
  • Search & Filter: Real-time filter by coin name or symbol
  • Network States: Polished animated skeletons and error recovery

01. Full Annotated Component Code

src/CryptoDashboard.jsx
import { useState, useEffect } from 'react';
import { useLocalStorage } from './hooks/useLocalStorage';

export default function CryptoDashboard() {
  const [coins, setCoins] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [search, setSearch] = useState('');
  
  // Persist pinned coin IDs in localStorage
  const [pinned, setPinned] = useLocalStorage('pinned_coins', ['bitcoin', 'ethereum']);

  async function fetchMarketData() {
    try {
      setLoading(true);
      setError(null);
      const url = 'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=12&page=1';
      const res = await fetch(url);
      if (!res.ok) throw new Error('Rate limit or API unavailable');
      const data = await res.json();
      setCoins(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    fetchMarketData();
  }, []);

  function togglePin(coinId) {
    setPinned(prev => 
      prev.includes(coinId) ? prev.filter(id => id !== coinId) : [...prev, coinId]
    );
  }

  const filteredCoins = coins.filter(c => 
    c.name.toLowerCase().includes(search.toLowerCase()) ||
    c.symbol.toLowerCase().includes(search.toLowerCase())
  );

  return (
    <div className="dashboard-container">
      <div className="dash-header">
        <h1>Live Market Ticker</h1>
        <button onClick={fetchMarketData} className="btn-refresh">↻ Refresh Prices</button>
      </div>

      <input 
        type="search" 
        placeholder="Filter by coin or symbol (BTC, Solana, ETH)..." 
        value={search} 
        onChange={e => setSearch(e.target.value)} 
        className="dash-search" 
      />

      {loading && <div className="loading-card">Updating live market feeds...</div>}
      {error && <div className="error-card">{error} <button onClick={fetchMarketData}>Retry</button></div>}

      {!loading && !error && (
        <div className="crypto-grid">
          {filteredCoins.map(coin => {
            const isPinned = pinned.includes(coin.id);
            const isPositive = coin.price_change_percentage_24h >= 0;

            return (
              <div key={coin.id} className={`crypto-card ${isPinned ? 'pinned' : ''}`}>
                <div className="coin-top">
                  <img src={coin.image} alt={coin.name} width="32" height="32" />
                  <div>
                    <h3>{coin.name}</h3>
                    <span className="symbol">{coin.symbol.toUpperCase()}</span>
                  </div>
                  <button onClick={() => togglePin(coin.id)} className="pin-btn">
                    {isPinned ? '★' : '☆'}
                  </button>
                </div>

                <div className="price-row">
                  <span className="price">${coin.current_price.toLocaleString()}</span>
                  <span className={`change ${isPositive ? 'up' : 'down'}`}>
                    {isPositive ? '+' : ''}{coin.price_change_percentage_24h?.toFixed(2)}%
                  </span>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}
🎯 Project 2 Extensions
  1. Add a currency switcher to view prices in EUR (€) or GBP (£) instead of USD ($).
  2. Add an auto-refresh timer that automatically triggers fetchMarketData() every 60 seconds using useEffect.
Lecture 17 · Global State & Production

Lifting State & The Context API

Solve the notorious "Prop Drilling" dilemma. Learn how to lift state up to common ancestors, and utilize React's built-in Context API to provide global state (such as user authentication, themes, and shopping carts) to any component in your tree without passing props.

What You Will Learn
  • The Prop Drilling problem and when it hurts maintainability
  • Lifting State Up: finding the nearest common parent component
  • The 3-step Context workflow: createContext, Context.Provider, and useContext
  • Building a production-ready AuthContext and ThemeContext
  • When to use Context vs when to stick to simple props

01. The Prop Drilling Problem

Imagine your root App component has a logged-in user object. A deep child component (e.g. UserAvatar nested inside Navbar -> NavMenu -> UserProfileSection -> UserAvatar) needs access to this user.

Without Context, every intermediate component must accept the user prop and forward it down, even if that intermediate component has zero use for it. This tedious, brittle pattern is known as Prop Drilling.

02. The Context API Solution

React's Context API allows you to "teleport" data directly to any component in the tree, regardless of depth:

src/context/ThemeContext.jsx
import { createContext, useContext, useState } from 'react';

// Step 1: Create the Context object
const ThemeContext = createContext();

// Step 2: Create a Custom Provider Component
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('dark');

  function toggleTheme() {
    setTheme(prev => prev === 'dark' ? 'light' : 'dark');
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <div className={`theme-wrapper ${theme}`}>
        {children}
      </div>
    </ThemeContext.Provider>
  );
}

// Step 3: Create a clean custom hook for consumption
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

03. Consuming Context Anywhere

Wrap your top-level app inside <ThemeProvider> in main.jsx or App.jsx:

main.jsx & DeepChild.jsx
// 1. Wrap the app tree in main.jsx
<ThemeProvider>
  <App />
</ThemeProvider>

// 2. Consume directly in ANY nested component without props!
import { useTheme } from './context/ThemeContext';

export default function DarkModeButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme}>
      Current Mode: {theme === 'dark' ? '🌙 Dark' : '☀️ Light'}
    </button>
  );
}

What problem does the React Context API primarily solve?

  1. It compiles JSX into pure machine code.
  2. It eliminates Prop Drilling by sharing data globally without passing props through intermediate components.
  3. It replaces all asynchronous fetch calls.

Context allows data (like user auth or themes) to be accessed by any component in the sub-tree without manual prop drilling through intermediary components.

🎯 Exercise 4.1: Build an AuthContext

Objective: Create an authentication provider and custom hook.

  1. Create AuthContext.jsx with states: user (null or { name: 'Alice', email: '[email protected]' }) and functions: login(userData) and logout().
  2. Wrap your app in <AuthProvider>.
  3. Build a Navbar that reads useAuth(): if logged in, display "Welcome, Alice" and a Log Out button. If null, display a Login button.
Lecture 18 · Global State & Production

Routing with React Router v6+

Transform your React application into a multi-page Single Page App. Master client-side routing with React Router v6+, dynamic URL parameters with useParams, programmatic redirects with useNavigate, and 404 catch-all pages.

What You Will Learn
  • Client-side routing vs traditional server multi-page architecture
  • Installing and setting up react-router-dom
  • Core components: BrowserRouter, Routes, Route, Link, and NavLink
  • Dynamic routes with URL parameters: /products/:productId and the useParams hook
  • Programmatic navigation with the useNavigate hook
  • Catch-all 404 Not Found route handling (path="*")

01. Installation & Basic Setup

In standard HTML websites, clicking a link causes the browser to download an entirely new HTML page from the server, causing a blank flash. React Router intercepts link clicks, updates the browser URL bar via the HTML5 History API, and swaps components instantly without refreshing the page.

Terminal
npm install react-router-dom

02. Defining Routes in Your App

App.jsx
import { BrowserRouter, Routes, Route, Link, NavLink } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import ProductDetail from './pages/ProductDetail';
import NotFound from './pages/NotFound';

export default function App() {
  return (
    <BrowserRouter>
      <nav className="main-nav">
        {/* NavLink automatically adds an "active" CSS class when matching current URL! */}
        <NavLink to="/" end>Home</NavLink>
        <NavLink to="/about">About</NavLink>
        <NavLink to="/contact">Contact</NavLink>
      </nav>

      <main className="page-container">
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          {/* Dynamic route parameter :id */}
          <Route path="/products/:id" element={<ProductDetail />} />
          {/* 404 Catch-All Route */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </main>
    </BrowserRouter>
  );
}

03. Reading Dynamic URL Parameters with useParams

When a user navigates to /products/item-849, how does the component know which item ID was requested? The useParams hook extracts URL tokens dynamically:

pages/ProductDetail.jsx
import { useParams, useNavigate } from 'react-router-dom';

export default function ProductDetail() {
  const { id } = useParams();
  const navigate = useNavigate();

  return (
    <div className="detail-card">
      <h1>Product ID: #{id}</h1>
      <p>Displaying live specifications for product #{id}...</p>
      
      {/* Programmatic navigation back to home */}
      <button onClick={() => navigate('/')}>
        ← Back to Catalog
      </button>
    </div>
  );
}

Why should you use <Link to="/about"> instead of <a href="/about"> in a React SPA?

  1. HTML anchor tags are forbidden by modern web standards.
  2. <Link> intercepts navigation to update the UI instantly without triggering a full-page server reload.
  3. <Link> encrypts the user's browser session.

<Link> prevents the browser from refreshing the entire page, preserving in-memory JavaScript state and delivering an instant Single Page Application experience.

🎯 Exercise 4.2: Build a Multi-Page Blog

Objective: Create a blog with home, post details, and 404 pages.

  1. Set up routes for / (lists 3 blog posts), /posts/:slug (post details), and * (404 page).
  2. Clicking a blog post card on Home links to its respective slug.
  3. The PostDetail page reads useParams() and displays the corresponding title and text.
  4. If the user enters an invalid URL, show a styled 404 page with a "Return Home" button.
Lecture 19 · Global State & Production

Performance Optimization: useMemo, useCallback & React.memo

Master optimizing React applications for high speed and scale. Understand when React re-renders, cache heavy calculations with useMemo, preserve function references with useCallback, prevent child re-renders with React.memo, and split code with React.lazy.

What You Will Learn
  • The exact conditions that trigger component re-renders
  • Caching expensive computations using the useMemo hook
  • Preserving stable function references across renders using useCallback
  • Skipping child re-renders with React.memo shallow comparison
  • Code splitting with React.lazy and Suspense to shrink bundle sizes

01. Why and When Does React Re-render?

A React component re-renders under two primary circumstances:

  1. Its internal state changes: Calling a state setter schedules a re-render.
  2. Its parent component re-renders: By default, whenever a parent component re-renders, ALL of its children re-render as well, even if their props have not changed!

02. Caching Calculations with useMemo

If you have a function that performs heavy mathematical calculations or sorts a list of 10,000 items, you don't want it recalculating on unrelated state changes (like typing into a search input):

MemoizedCalculation.jsx
import { useState, useMemo } from 'react';

function heavyCalculation(num) {
  console.log('Executing expensive calculation...');
  let total = 0;
  for (let i = 0; i < 50000000; i++) { total += num; }
  return total;
}

export default function ComputeBox() {
  const [number, setNumber] = useState(10);
  const [dark, setDark] = useState(false);

  // useMemo only recalculates when "number" changes, NOT when "dark" toggles!
  const calculationResult = useMemo(() => {
    return heavyCalculation(number);
  }, [number]);

  return (
    <div className={dark ? 'dark' : 'light'}>
      <h3>Result: {calculationResult}</h3>
      <button onClick={() => setNumber(n => n + 1)}>Change Number</button>
      <button onClick={() => setDark(d => !d)}>Toggle Theme (Fast!)</button>
    </div>
  );
}

03. Preserving Callbacks with useCallback

Every time a component re-renders, any function defined inside it is created as a brand-new function in memory. Passing this new function down as a prop breaks React.memo child optimizations:

useCallbackExample.jsx
import React, { useState, useCallback } from 'react';

// Child only re-renders if onItemDelete changes!
const TaskItem = React.memo(({ task, onItemDelete }) => {
  console.log('Rendered TaskItem:', task.text);
  return <li onClick={() => onItemDelete(task.id)}>{task.text}</li>;
});

export default function TaskList() {
  const [tasks, setTasks] = useState([...]);

  // useCallback keeps the exact same function reference between renders
  const handleDelete = useCallback((id) => {
    setTasks(prev => prev.filter(t => t.id !== id));
  }, []);

  return (
    <ul>
      {tasks.map(t => <TaskItem key={t.id} task={t} onItemDelete={handleDelete} />)}
    </ul>
  );
}

04. Code Splitting with React.lazy & Suspense

Why make your users download a 5MB bundle when they only visit the Home page? Lazy-load heavier routes on demand:

LazyRoutes.jsx
import React, { Suspense, lazy } from 'react';

// Components are loaded only when the user actually navigates to them
const AnalyticsDashboard = lazy(() => import('./pages/Analytics'));

function App() {
  return (
    <Suspense fallback={<div className="page-skeleton">Loading page...</div>}>
      <AnalyticsDashboard />
    </Suspense>
  );
}

What is the difference between useMemo and useCallback?

  1. useMemo caches the calculated result of a function, while useCallback caches the function definition itself.
  2. useMemo is for asynchronous API calls, while useCallback is for synchronous math.
  3. They are identical aliases for the exact same function.

useMemo(() => fn(), [deps]) caches the returned value of a calculation. useCallback(fn, [deps]) caches the function instance itself across renders.

🎯 Exercise 4.3: Optimize a Filtered List

Objective: Profile and memoize a searchable dataset.

  1. Generate a mock array of 2,000 items with random prices and categories.
  2. Wrap the filtering logic in useMemo tied to the search query.
  3. Add a separate unrelated counter button. Verify in the console that filtering does NOT run when clicking the counter!
Lecture 20 · Capstone Project

🛠️ Capstone Project: Full-Featured E-Commerce Store

Congratulations on reaching the final capstone! In this comprehensive project, you will combine all 20 lectures—Components, Props, useState, Events, Conditional Rendering, Lists & Keys, Forms, useEffect, Custom Hooks, Context API, and Routing—into a production-grade E-Commerce Store.

Capstone Architecture & Features
  • Product Catalog: Dynamic grid with live category filters, search input, and sorting
  • CartContext & Global State: Add to cart, quantity adjustments, item removal, and subtotal calculation
  • Persistent Storage: Cart state automatically persisted using custom useLocalStorage
  • Multi-Page Routing: Catalog page, Product details, Shopping Cart drawer, and Checkout Modal
  • Toast Notifications: Real-time feedback ("Added to cart!")

01. The Global CartContext

Create src/context/CartContext.jsx to manage shopping cart operations globally across all pages:

src/context/CartContext.jsx
import { createContext, useContext, useState, useEffect } from 'react';

const CartContext = createContext();

export function CartProvider({ children }) {
  const [cart, setCart] = useState(() => {
    try {
      const saved = localStorage.getItem('cart_items');
      return saved ? JSON.parse(saved) : [];
    } catch (e) { return []; }
  });

  useEffect(() => {
    localStorage.setItem('cart_items', JSON.stringify(cart));
  }, [cart]);

  function addToCart(product) {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => 
          item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item
        );
      }
      return [...prev, { ...product, quantity: 1 }];
    });
  }

  function removeFromCart(productId) {
    setCart(prev => prev.filter(item => item.id !== productId));
  }

  function updateQuantity(productId, delta) {
    setCart(prev => prev.map(item => {
      if (item.id === productId) {
        const newQty = item.quantity + delta;
        return newQty > 0 ? { ...item, quantity: newQty } : item;
      }
      return item;
    }));
  }

  function clearCart() {
    setCart([]);
  }

  const cartCount = cart.reduce((sum, item) => sum + item.quantity, 0);
  const cartTotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);

  return (
    <CartContext.Provider value={{
      cart,
      addToCart,
      removeFromCart,
      updateQuantity,
      clearCart,
      cartCount,
      cartTotal
    }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  return useContext(CartContext);
}

02. Product Catalog & Filter Page

src/pages/Catalog.jsx
import { useState } from 'react';
import { useCart } from '../context/CartContext';

const PRODUCTS = [
  { id: 'p-1', name: 'Wireless Noise-Canceling Headphones', price: 149.99, category: 'Audio', rating: 4.8 },
  { id: 'p-2', name: 'Mechanical RGB Keyboard (Hot-swap)', price: 99.50, category: 'Peripherals', rating: 4.9 },
  { id: 'p-3', name: 'Ultra-Wide 34-inch 144Hz Monitor', price: 449.00, category: 'Displays', rating: 4.7 },
  { id: 'p-4', name: 'Ergonomic Aluminum Laptop Stand', price: 39.99, category: 'Accessories', rating: 4.6 },
  { id: 'p-5', name: 'Studio Condenser USB Microphone', price: 89.00, category: 'Audio', rating: 4.8 }
];

export default function Catalog() {
  const { addToCart } = useCart();
  const [category, setCategory] = useState('All');
  const [search, setSearch] = useState('');
  const [toast, setToast] = useState('');

  function handleAdd(product) {
    addToCart(product);
    setToast(`Added "${product.name}" to cart!`);
    setTimeout(() => setToast(''), 3000);
  }

  const filtered = PRODUCTS.filter(p => {
    const matchCat = category === 'All' || p.category === category;
    const matchSearch = p.name.toLowerCase().includes(search.toLowerCase());
    return matchCat && matchSearch;
  });

  return (
    <div className="catalog-view">
      {toast && <div className="toast-notification">✓ {toast}</div>}

      <div className="filter-bar">
        <input 
          type="search" 
          placeholder="Search gear..." 
          value={search} 
          onChange={e => setSearch(e.target.value)} 
        />
        <div className="pill-group">
          {['All', 'Audio', 'Peripherals', 'Displays', 'Accessories'].map(cat => (
            <button 
              key={cat} 
              className={category === cat ? 'pill active' : 'pill'} 
              onClick={() => setCategory(cat)}
            >
              {cat}
            </button>
          ))}
        </div>
      </div>

      <div className="product-grid">
        {filtered.map(product => (
          <div key={product.id} className="product-card">
            <div className="tag">{product.category}</div>
            <h3>{product.name}</h3>
            <div className="rating">★ {product.rating} / 5.0</div>
            <div className="card-footer">
              <span className="price">${product.price.toFixed(2)}</span>
              <button onClick={() => handleAdd(product)} className="btn-add">
                + Add to Cart
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

03. Shopping Cart Drawer & Checkout

src/components/CartDrawer.jsx
import { useCart } from '../context/CartContext';

export default function CartDrawer({ isOpen, onClose }) {
  const { cart, removeFromCart, updateQuantity, cartTotal, clearCart } = useCart();

  if (!isOpen) return null;

  return (
    <div className="drawer-overlay" onClick={onClose}>
      <div className="drawer-content" onClick={e => e.stopPropagation()}>
        <div className="drawer-head">
          <h2>Your Shopping Cart</h2>
          <button onClick={onClose} className="close-btn">×</button>
        </div>

        <div className="cart-items-scroll">
          {cart.length === 0 ? (
            <div className="cart-empty">Your cart is currently empty.</div>
          ) : (
            cart.map(item => (
              <div key={item.id} className="cart-item-row">
                <div className="item-info">
                  <h4>{item.name}</h4>
                  <span>${item.price.toFixed(2)} each</span>
                </div>

                <div className="qty-controls">
                  <button onClick={() => updateQuantity(item.id, -1)}>-</button>
                  <span>{item.quantity}</span>
                  <button onClick={() => updateQuantity(item.id, 1)}>+</button>
                </div>

                <button onClick={() => removeFromCart(item.id)} className="remove-btn">
                  🗑
                </button>
              </div>
            ))
          )}
        </div>

        {cart.length > 0 && (
          <div className="drawer-foot">
            <div className="total-row">
              <span>Subtotal:</span>
              <strong>${cartTotal.toFixed(2)}</strong>
            </div>
            <button 
              onClick={() => { alert('Order placed successfully! Thank you.'); clearCart(); onClose(); }} 
              className="checkout-btn"
            >
              Proceed to Checkout (${cartTotal.toFixed(2)})
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

04. Your React Developer Certification Path

Congratulations on completing the React Mastery Course on Code Tutorium! You now possess comprehensive, practical mastery of:

  • Declarative Component-driven architecture and Virtual DOM diffing
  • Modern tooling with Vite, ES Modules, and production bundling
  • JSX formatting, fragments, and expression evaluation
  • Unidirectional data flow, destructuring props, and children slots
  • Interactive state management with useState and immutable state recipes
  • Controlled forms, multi-field state objects, and form validation
  • Component lifecycles, REST API consumption, and cleanup with useEffect
  • Direct DOM refs and mutable instance variables with useRef
  • Extracting custom hooks for DRY, reusable application architecture
  • Global state management without third-party boilerplate using the Context API
  • Multi-page Single Page App navigation using React Router v6+
  • Optimized performance using useMemo, useCallback, React.memo, and code splitting
Next Steps in Your Career

Deploy your capstone project live to Vercel or Netlify, link your GitHub repository, and add it to your portfolio resume. You are now prepared to build production web applications or explore full-stack frameworks like Next.js!