Documentation

Complete guide to React from basics to advanced

Module 1: Introduction to React
What is React?

React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It was created by Jordan Walke, a software engineer at Facebook, and was first deployed on Facebook's newsfeed in 2011 and later on Instagram in 2012.

Core Concepts
  • Components - Building blocks that encapsulate markup, logic, and styling
  • Props - Read-only inputs passed to components
  • State - Mutable data that determines component rendering
  • Virtual DOM - JavaScript representation of the real DOM
  • Unidirectional Data Flow - Data flows from parent to child components
Why React?
Performance Benefits

React's Virtual DOM enables efficient updates by comparing the new virtual tree with the previous one and calculating the minimal set of changes required to update the real DOM.

Developer Experience

React provides excellent developer tools, hot reloading, and a component-based architecture that makes code more maintainable and reusable.

Ecosystem

React has a vast ecosystem of libraries, tools, and community support, making it easy to find solutions to common problems.

React Philosophy

React follows several key principles:

  • Composition over Inheritance - Prefer composing components rather than using inheritance
  • Single Responsibility - Each component should have one reason to change
  • Immutability - Treat state as immutable to prevent unexpected side effects
  • Pure Functions - Components should be pure functions of their props and state
React vs Other Frameworks
Feature React Vue Angular
Learning Curve Moderate Easy Steep
Performance Excellent Good Good
Flexibility High Moderate Low
Bundle Size Small Small Large
Module 2: Setup and Installation
Prerequisites

Before starting with React, ensure you have:

  • Node.js (version 14 or higher)
  • npm (comes with Node.js) or yarn
  • Basic knowledge of HTML, CSS, and JavaScript
  • Familiarity with ES6+ features (arrow functions, destructuring, classes)
Installation Methods
Method 1: Create React App (Recommended for Beginners)
# Install globally (optional)
npm install -g create-react-app

# Create a new React application
npx create-react-app my-react-app
cd my-react-app

# Start the development server
npm start

# Build for production
npm run build

# Run tests
npm test
Method 2: Vite (Modern and Fast)
# Create a new React project with Vite
npm create vite@latest my-vite-app -- --template react
cd my-vite-app

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview
Method 3: Next.js (Full-Stack Framework)
# Create a new Next.js application
npx create-next-app@latest my-next-app
cd my-next-app

# Start development server
npm run dev

# Build for production
npm run build

# Start production server
npm start
Project Structure

A typical React project structure:

my-react-app/
├── public/                 # Static files
│   ├── index.html         # Main HTML file
│   ├── favicon.ico        # Favicon
│   └── manifest.json      # PWA configuration
├── src/                   # Source code
│   ├── components/        # Reusable components
│   ├── pages/            # Page components
│   ├── hooks/            # Custom hooks
│   ├── utils/            # Utility functions
│   ├── styles/           # CSS/SCSS files
│   ├── assets/           # Images, fonts, etc.
│   ├── App.js            # Main App component
│   ├── index.js          # Entry point
│   └── index.css         # Global styles
├── package.json          # Dependencies and scripts
├── package-lock.json     # Lock file for dependencies
└── README.md             # Project documentation
Essential VS Code Extensions
  • ES7+ React/Redux/React-Native snippets - Code snippets for React
  • Prettier - Code formatter - Automatic code formatting
  • ESLint - Code linting and error detection
  • Auto Rename Tag - Auto rename paired HTML/XML tags
  • Bracket Pair Colorizer - Colorize matching brackets
Environment Variables

Create a .env file in the root directory:

# Environment variables must start with REACT_APP_
REACT_APP_API_URL=http://localhost:3001/api
REACT_APP_ENVIRONMENT=development
REACT_APP_VERSION=1.0.0

# Access in React
process.env.REACT_APP_API_URL
Module 3: JSX - JavaScript XML
Understanding JSX

JSX is a syntax extension for JavaScript that allows you to write HTML-like code in your JavaScript files. It makes React components more readable and expressive by combining markup with logic.

Basic JSX Syntax
// JSX element
const element = <h1>Hello, World!</h1>;

// JSX with expressions
const name = "John";
const element = <h1>Hello, {name}</h1>;

// JSX with function calls
function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = { firstName: 'John', lastName: 'Doe' };
const element = <h1>Hello, {formatName(user)}!</h1>;

// JSX with object properties
const element = <img src={user.avatarUrl} alt={user.name} />;
JSX Rules and Best Practices
1. Single Parent Element

JSX expressions must have one parent element. Use React.Fragment to avoid adding extra nodes to the DOM.

// Incorrect - Multiple parent elements
const element = (
  <h1>Hello</h1>
  <p>World</p>
);

// Correct - Single parent element
const element = (
  <div>
    <h1>Hello</h1>
    <p>World</p>
  </div>
);

// Best - Using Fragment
const element = (
  <React.Fragment>
    <h1>Hello</h1>
    <p>World</p>
  </React.Fragment>
);

// Shorthand Fragment syntax
const element = (
  <>
    <h1>Hello</h1>
    <p>World</p>
  </>
);
2. Attribute Naming

Use camelCase for attributes and className instead of class.

// HTML
<div class="container" onclick="handleClick()">

// JSX
<div className="container" onClick={handleClick}>
3. Self-Closing Tags

All tags must be properly closed, including self-closing tags.

// Correct
<img src="logo.png" alt="Logo" />
<br />
<input type="text" />

// Incorrect
<img src="logo.png" alt="Logo">
<br>
<input type="text">
JavaScript Expressions in JSX
// Variables
const name = "John";
const element = <h1>Hello, {name}</h1>;

// Function calls
const element = <h1>{formatName(user)}</h1>;

// Object properties
const element = <img src={user.avatarUrl} />;

// Ternary operators
const element = <div>{isLoggedIn ? <Profile /> : <Login />}</div>;

// Logical operators
const element = <div>{unreadMessages.length > 0 && <NewMessages />}</div>;

// Map for lists
const items = ['Apple', 'Banana', 'Orange'];
const listItems = items.map((item, index) => 
  <li key={index}>{item}</li>
);
JSX and TypeScript

When using TypeScript with React (.tsx files):

interface User {
  name: string;
  age: number;
}

const UserCard: React.FC<{ user: User }> = ({ user }) => {
  return (
    <div className="user-card">
      <h2>{user.name}</h2>
      <p>Age: {user.age}</p>
    </div>
  );
};
Common JSX Patterns
// Conditional rendering
function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <h1>Welcome back!</h1>
      ) : (
        <h1>Please sign in.</h1>
      )}
    </div>
  );
}

// List rendering
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          {todo.completed && <span className="completed"> ✓</span>}
        </li>
      ))}
    </ul>
  );
}

// Spreading props
function App() {
  const props = { className: 'button', disabled: false };
  return <button {...props}>Click me</button>;
}
Module 4: Components
Understanding Components

Components are the fundamental building blocks of React applications. They are independent, reusable pieces of UI that can accept inputs (props) and return React elements describing what should appear on screen.

Function Components

Function components are the modern way to write React components. They are simpler and more concise than class components.

// Basic function component
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// Arrow function component
const Welcome = (props) => {
  return <h1>Hello, {props.name}</h1>;
};

// Concise arrow function
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;

// Using the component
function App() {
  return (
    <div>
      <Welcome name="Alice" />
      <Welcome name="Bob" />
      <Welcome name="Charlie" />
    </div>
  );
}
Class Components

Class components are the traditional way to write React components. They have more features like lifecycle methods and state management.

import React from 'react';

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// Class component with state and lifecycle
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    console.log('Component mounted');
  }

  componentDidUpdate() {
    console.log('Component updated');
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}
Props (Properties)

Props are read-only inputs passed to components. They allow components to be reusable and configurable.

// Passing props
function App() {
  return <UserCard name="John" age={30} isActive={true} />;
}

// Accessing props
function UserCard(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>Age: {props.age}</p>
      <p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
    </div>
  );
}

// Destructuring props
function UserCard({ name, age, isActive }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
    </div>
  );
}

// Default props
function UserCard({ name = 'Guest', age = 0, isActive = false }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
    </div>
  );
}

// PropTypes for validation
import PropTypes from 'prop-types';

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  isActive: PropTypes.bool
};
Component Composition

Components can be composed together to create complex UIs from simple building blocks.

// Child components
function Avatar({ user, size }) {
  return (
    <img
      className="avatar"
      src={user.avatarUrl}
      alt={user.name}
      width={size}
      height={size}
    />
  );
}

function UserInfo({ user }) {
  return (
    <div className="user-info">
      <Avatar user={user} size={50} />
      <div className="user-details">
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
    </div>
  );
}

// Parent component
function ProfilePage() {
  const user = {
    name: 'John Doe',
    email: 'john@example.com',
    avatarUrl: 'https://example.com/avatar.jpg'
  };

  return (
    <div className="profile-page">
      <UserInfo user={user} />
      <div className="profile-content">
        {/* Additional profile content */}
      </div>
    </div>
  );
}
Children Prop

The special children prop allows components to pass components or elements directly between their opening and closing tags.

// Component that accepts children
function Card({ title, children }) {
  return (
    <div className="card">
      <h2 className="card-title">{title}</h2>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}

// Using the component
function App() {
  return (
    <Card title="User Profile">
      <p>This is the user profile card content.</p>
      <button>Edit Profile</button>
    </Card>
  );
}

// Component that manipulates children
function List({ children }) {
  return (
    <ul className="list">
      {React.Children.map(children, (child, index) => (
        <li key={index} className="list-item">
          {child}
        </li>
      ))}
    </ul>
  );
}
Component Best Practices
Single Responsibility

Each component should have one reason to change and do one thing well.

Keep Components Small

Smaller components are easier to understand, test, and reuse.

Use Descriptive Names

Component names should clearly describe what they do.

Avoid Inline Functions

Define functions outside the render method when possible to avoid unnecessary re-renders.

Module 5: State
Understanding State

State is a built-in object that stores property values belonging to a component. When state changes, the component re-renders to reflect the new state. State is mutable and should be managed carefully.

useState Hook

The useState hook is the most common way to manage state in function components.

import React, { useState } from 'react';

// Basic state usage
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

// Multiple state variables
function UserProfile() {
  const [name, setName] = useState('John Doe');
  const [age, setAge] = useState(30);
  const [email, setEmail] = useState('john@example.com');

  const handleNameChange = (e) => setName(e.target.value);
  const handleAgeChange = (e) => setAge(parseInt(e.target.value));
  const handleEmailChange = (e) => setEmail(e.target.value);

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={handleNameChange}
        placeholder="Name"
      />
      <input
        type="number"
        value={age}
        onChange={handleAgeChange}
        placeholder="Age"
      />
      <input
        type="email"
        value={email}
        onChange={handleEmailChange}
        placeholder="Email"
      />
      <p>Name: {name}, Age: {age}, Email: {email}</p>
    </div>
  );
}
State Update Patterns
// Direct update (for simple values)
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

// Functional update (when new state depends on previous state)
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => setCount(prevCount => prevCount + 1);
  const decrement = () => setCount(prevCount => prevCount - 1);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// Updating objects in state
function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUser(prevUser => ({
      ...prevUser,
      [name]: value
    }));
  };

  return (
    <form>
      <input
        name="name"
        value={user.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        name="email"
        value={user.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="age"
        value={user.age}
        onChange={handleChange}
        placeholder="Age"
      />
    </form>
  );
}

// Updating arrays in state
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = () => {
    if (inputValue.trim()) {
      setTodos(prevTodos => [
        ...prevTodos,
        { id: Date.now(), text: inputValue, completed: false }
      ]);
      setInputValue('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const deleteTodo = (id) => {
    setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="Add a todo"
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
State in Class Components
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: 'Counter'
    };
  }

  // setState with object
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  // setState with function (recommended)
  decrement = () => {
    this.setState(prevState => ({
      count: prevState.count - 1
    }));
  };

  // setState with callback
  updateName = () => {
    this.setState(
      { name: 'Updated Counter' },
      () => {
        console.log('State updated:', this.state.name);
      }
    );
  };

  render() {
    return (
      <div>
        <h2>{this.state.name}</h2>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.updateName}>Update Name</button>
      </div>
    );
  }
}
State Management Best Practices
Keep State Local

Only lift state up when multiple components need to share it.

Use Multiple State Variables

Separate unrelated state into different useState calls.

Average State Updates

Use functional updates when the new state depends on the previous state.

Immutable Updates

Always create new objects/arrays when updating state to avoid mutation.

Common State Patterns
// Toggle pattern
function Toggle() {
  const [isOn, setIsOn] = useState(false);
  
  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

// Loading pattern
function DataLoader() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const loadData = async () => {
    setLoading(true);
    setError(null);
    
    try {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={loadData} disabled={loading}>
        {loading ? 'Loading...' : 'Load Data'}
      </button>
      {error && <p>Error: {error}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

// Form pattern
function Form() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
    rememberMe: false
  });

  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const validate = () => {
    const newErrors = {};
    if (!formData.username) newErrors.username = 'Username is required';
    if (!formData.password) newErrors.password = 'Password is required';
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      console.log('Form submitted:', formData);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="Username"
      />
      {errors.username && <span>{errors.username}</span>}
      
      <input
        name="password"
        type="password"
        value={formData.password}
        onChange={handleChange}
        placeholder="Password"
      />
      {errors.password && <span>{errors.password}</span>}
      
      <label>
        <input
          name="rememberMe"
          type="checkbox"
          checked={formData.rememberMe}
          onChange={handleChange}
        />
        Remember me
      </label>
      
      <button type="submit">Submit</button>
    </form>
  );
}
Module 6: React Hooks
Introduction to Hooks

Hooks are functions that let you "hook into" React state and lifecycle features from function components. They were introduced in React 16.8 and allow you to use state and other React features without writing a class.

Rules of Hooks
1. Only Call Hooks at the Top Level

Don't call Hooks inside loops, conditions, or nested functions. Always use Hooks at the top level of your React function.

2. Only Call Hooks from React Functions

Call Hooks from React function components or from custom Hooks (not from regular JavaScript functions).

Built-in Hooks
useState
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <p>Name: {name}</p>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
      />
    </div>
  );
}
useEffect
import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  // Effect runs after every render
  useEffect(() => {
    console.log('Component rendered');
  });

  // Effect runs only once (on mount)
  useEffect(() => {
    console.log('Component mounted');
    return () => {
      console.log('Component will unmount');
    };
  }, []);

  // Effect runs when count changes
  useEffect(() => {
    console.log('Count changed');
  }, [count]);

  // Effect with cleanup
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Timer tick');
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  // Data fetching
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Error fetching data:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  return <div>{JSON.stringify(data)}</div>;
}
useContext
import { createContext, useContext } from 'react';

// Create context
const ThemeContext = createContext('light');

// Provider component
function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// Consumer component
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button 
      onClick={toggleTheme}
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff'
      }}
    >
      Toggle Theme
    </button>
  );
}
useReducer
import { useReducer } from 'react';

// Reducer function
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
};

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

// Complex reducer with initialization
const initialState = {
  todos: [],
  filter: 'all'
};

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'add_todo':
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload, completed: false }]
      };
    case 'toggle_todo':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
        )
      };
    case 'set_filter':
      return { ...state, filter: action.payload };
    default:
      return state;
  }
};

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (input.trim()) {
      dispatch({ type: 'add_todo', payload: input });
      setInput('');
    }
  };

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && addTodo()}
        placeholder="Add a todo"
      />
      <button onClick={addTodo}>Add</button>
      
      <div>
        <button onClick={() => dispatch({ type: 'set_filter', payload: 'all' })}>All</button>
        <button onClick={() => dispatch({ type: 'set_filter', payload: 'active' })}>Active</button>
        <button onClick={() => dispatch({ type: 'set_filter', payload: 'completed' })}>Completed</button>
      </div>
      
      <ul>
        {state.todos
          .filter(todo => {
            if (state.filter === 'active') return !todo.completed;
            if (state.filter === 'completed') return todo.completed;
            return true;
          })
          .map(todo => (
            <li key={todo.id}>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => dispatch({ type: 'toggle_todo', payload: todo.id })}
              />
              {todo.text}
            </li>
          ))}
      </ul>
    </div>
  );
}
useCallback
import { useState, useCallback } from 'react';

function Button({ onClick, children }) {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
}

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  // Without useCallback - Button re-renders on every render
  const handleClickWithoutCallback = () => {
    setCount(count + 1);
  };

  // With useCallback - Button only re-renders when count changes
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={handleClick}>Increment</Button>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
    </div>
  );
}
useMemo
import { useState, useMemo } from 'react';

function ExpensiveCalculation({ numbers }) {
  const [filter, setFilter] = useState('');

  // Expensive calculation without memoization
  const expensiveSum = numbers.reduce((sum, num) => sum + num * num, 0);

  // With memoization - only recalculates when numbers change
  const memoizedSum = useMemo(() => {
    console.log('Calculating sum...');
    return numbers.reduce((sum, num) => sum + num * num, 0);
  }, [numbers]);

  // Filtered list with memoization
  const filteredNumbers = useMemo(() => {
    console.log('Filtering numbers...');
    return numbers.filter(num => num.toString().includes(filter));
  }, [numbers, filter]);

  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter numbers"
      />
      <p>Sum of squares: {memoizedSum}</p>
      <ul>
        {filteredNumbers.map((num, index) => (
          <li key={index}>{num}</li>
        ))}
      </ul>
    </div>
  );
}
useRef
import { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

// Storing previous value
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Custom Hooks
// Custom hook for local storage
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// Usage
function App() {
  const [name, setName] = useLocalStorage('name', 'Guest');

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <p>Hello, {name}!</p>
    </div>
  );
}

// Custom hook for API calls
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useApi(`https://api.example.com/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user?.name}</div>;
}

// Custom hook for debouncing
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// Usage
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      // Perform search with debounced term
      console.log('Searching for:', debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
}
Module 7: Event Handling
Understanding Events in React

React events are handled similarly to DOM events but with some syntax differences. React uses camelCase for event names and passes functions as event handlers rather than strings.

Basic Event Handling
function Button() {
  const handleClick = () => {
    alert('Button clicked!');
  };

  return <button onClick={handleClick}>Click me</button>;
}

// Arrow function in JSX
function Button() {
  return (
    <button onClick={() => alert('Button clicked!')}>
      Click me
    </button>
  );
}

// With parameters
function Button() {
  const handleClick = (name) => {
    alert(`Hello, ${name}!`);
  };

  return (
    <button onClick={() => handleClick('John')}>
      Say Hello
    </button>
  );
}
Common React Events
Category Events
Clipboard onCopy, onCut, onPaste
Keyboard onKeyDown, onKeyPress, onKeyUp
Focus onFocus, onBlur
Form onChange, onInput, onSubmit
Mouse onClick, onContextMenu, onDoubleClick, onDrag, onMouseDown, onMouseEnter, onMouseLeave, onMouseMove, onMouseOut, onMouseOver, onMouseUp
Touch onTouchCancel, onTouchEnd, onTouchMove, onTouchStart
UI onScroll, onResize
Wheel onWheel
Event Object
function EventExample() {
  const handleClick = (event) => {
    console.log('Event type:', event.type);
    console.log('Target:', event.target);
    console.log('Current target:', event.currentTarget);
    console.log('Native event:', event.nativeEvent);
  };

  const handleChange = (event) => {
    console.log('Input value:', event.target.value);
    console.log('Input name:', event.target.name);
  };

  const handleSubmit = (event) => {
    event.preventDefault(); // Prevent default form submission
    console.log('Form submitted');
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
      <input onChange={handleChange} placeholder="Type something" />
      <form onSubmit={handleSubmit}>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
Form Events
function FormExample() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: ''
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: value
    }));
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form data:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Username:</label>
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label>Password:</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          required
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}
Keyboard Events
function KeyboardExample() {
  const [key, setKey] = useState('');
  const [keyCode, setKeyCode] = useState('');

  const handleKeyDown = (event) => {
    setKey(event.key);
    setKeyCode(event.keyCode);
    console.log('Key pressed:', event.key);
    console.log('Key code:', event.keyCode);
    console.log('Shift key:', event.shiftKey);
    console.log('Ctrl key:', event.ctrlKey);
    console.log('Alt key:', event.altKey);
  };

  const handleKeyPress = (event) => {
    console.log('Key pressed (character):', event.charCode);
  };

  const handleKeyUp = (event) => {
    console.log('Key released:', event.key);
  };

  return (
    <div>
      <input
        type="text"
        onKeyDown={handleKeyDown}
        onKeyPress={handleKeyPress}
        onKeyUp={handleKeyUp}
        placeholder="Press any key"
      />
      <p>Last key pressed: {key}</p>
      <p>Key code: {keyCode}</p>
    </div>
  );
}

// Enter key submission
function SearchBox() {
  const [query, setQuery] = useState('');

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      handleSearch();
    }
  };

  const handleSearch = () => {
    console.log('Searching for:', query);
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onKeyDown={handleKeyDown}
        placeholder="Search..."
      />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}
Mouse Events
function MouseExample() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);

  const handleMouseMove = (event) => {
    setPosition({ x: event.clientX, y: event.clientY });
  };

  const handleMouseDown = () => {
    setIsDragging(true);
  };

  const handleMouseUp = () => {
    setIsDragging(false);
  };

  const handleMouseEnter = () => {
    console.log('Mouse entered');
  };

  const handleMouseLeave = () => {
    console.log('Mouse left');
  };

  return (
    <div>
      <div
        style={{
          width: 200,
          height: 200,
          backgroundColor: isDragging ? 'red' : 'blue',
          cursor: isDragging ? 'grabbing' : 'grab'
        }}
        onMouseMove={handleMouseMove}
        onMouseDown={handleMouseDown}
        onMouseUp={handleMouseUp}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        Drag me!
      </div>
      <p>Position: {position.x}, {position.y}</p>
      <p>Dragging: {isDragging ? 'Yes' : 'No'}</p>
    </div>
  );
}
Event Delegation
function EventDelegation() {
  const handleClick = (event) => {
    const buttonId = event.target.dataset.buttonId;
    if (buttonId) {
      console.log(`Button ${buttonId} clicked`);
    }
  };

  return (
    <div onClick={handleClick}>
      <button data-button-id="1">Button 1</button>
      <button data-button-id="2">Button 2</button>
      <button data-button-id="3">Button 3</button>
    </div>
  );
}
Custom Events
// Custom event hook
function useCustomEvent(eventName, handler) {
  useEffect(() => {
    window.addEventListener(eventName, handler);
    return () => {
      window.removeEventListener(eventName, handler);
    };
  }, [eventName, handler]);
}

// Usage
function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  const handleResize = () => {
    setSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
  };

  useCustomEvent('resize', handleResize);

  return (
    <div>
      <p>Window width: {size.width}px</p>
      <p>Window height: {size.height}px</p>
    </div>
  );
}
Event Performance
Throttling and Debouncing

For performance-critical events like scroll or resize, use throttling or debouncing.

Passive Event Listeners

Use passive event listeners for better scrolling performance.

Event Pooling

React pools event objects for performance. Access event properties asynchronously with care.

Module 8: Conditional Rendering
Understanding Conditional Rendering

Conditional rendering in React allows you to render different components or elements based on certain conditions. This is similar to using conditional statements in JavaScript but applied to JSX.

Using if Statements
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  
  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }
  return <h1>Please sign in.</h1>;
}

// Using if-else if-else
function getStatus(status) {
  if (status === 'loading') {
    return <div>Loading...</div>;
  } else if (status === 'success') {
    return <div>Success!</div>;
  } else if (status === 'error') {
    return <div>Error occurred!</div>;
  } else {
    return <div>Unknown status</div>;
  }
}
Using Ternary Operators
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
    </div>
  );
}

// Nested ternary
function Message({ type, text }) {
  return (
    <div className={`message ${type}`}>
      {type === 'success' ? 
        <span>✓ {text}</span> : 
        type === 'error' ? 
        <span>✗ {text}</span> : 
        <span>ℹ {text}</span>
      }
    </div>
  );
}
Using Logical AND Operator
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

// Multiple conditions
function Notification({ notifications }) {
  return (
    <div>
      {notifications.length > 0 && (
        <div className="notification-badge">
          {notifications.length}
        </div>
      )}
      {notifications.length > 0 && notifications.some(n => n.urgent) && (
        <div className="urgent-indicator">!</div>
      )}
    </div>
  );
}
Element Variables
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  let button;
  
  if (isLoggedIn) {
    button = <button>Logout</button>;
  } else {
    button = <button>Login</button>;
  }
  
  return (
    <div>
      <h1>Welcome!</h1>
      {button}
    </div>
  );
}

// Complex conditional rendering
function UserCard({ user, showDetails }) {
  let detailsSection;
  
  if (showDetails) {
    detailsSection = (
      <div className="user-details">
        <p>Email: {user.email}</p>
        <p>Phone: {user.phone}</p>
        <p>Address: {user.address}</p>
      </div>
    );
  }
  
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.bio}</p>
      {detailsSection}
    </div>
  );
}
Preventing Component Rendering
function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }
  
  return (
    <div className="warning">
      Warning!
    </div>
  );
}

// Usage
function App() {
  const [showWarning, setShowWarning] = useState(true);
  
  return (
    <div>
      <WarningBanner warn={showWarning} />
      <button onClick={() => setShowWarning(!showWarning)}>
        {showWarning ? 'Hide' : 'Show'} Warning
      </button>
    </div>
  );
}
Conditional Rendering with Lists
function TaskList({ tasks }) {
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id} className={task.completed ? 'completed' : 'active'}>
          <span>{task.text}</span>
          {task.completed && <span className="check-mark"> ✓</span>}
          {!task.completed && task.priority === 'high' && (
            <span className="priority-indicator"> High Priority</span>
          )}
        </li>
      ))}
    </ul>
  );
}

// Conditional rendering with empty state
function ProductList({ products }) {
  if (products.length === 0) {
    return (
      <div className="empty-state">
        <h3>No products found</h3>
        <p>Try adjusting your filters or search criteria.</p>
      </div>
    );
  }
  
  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}
Inline Conditional Styles
function StatusIndicator({ status }) {
  const getStatusStyle = () => {
    switch (status) {
      case 'active':
        return { color: 'green', fontWeight: 'bold' };
      case 'inactive':
        return { color: 'red', fontWeight: 'bold' };
      case 'pending':
        return { color: 'orange', fontWeight: 'bold' };
      default:
        return { color: 'gray' };
    }
  };
  
  return (
    <span style={getStatusStyle()}>
      {status.toUpperCase()}
    </span>
  );
}

// Conditional classes
function Button({ variant, size, disabled, children }) {
  const getButtonClasses = () => {
    let classes = ['btn'];
    
    if (variant) classes.push(`btn-${variant}`);
    if (size) classes.push(`btn-${size}`);
    if (disabled) classes.push('btn-disabled');
    
    return classes.join(' ');
  };
  
  return (
    <button 
      className={getButtonClasses()}
      disabled={disabled}
    >
      {children}
    </button>
  );
}
Higher-Order Components for Conditional Rendering
// HOC for conditional rendering
function withConditionalLoading(Component) {
  return function WithConditionalLoading({ isLoading, ...props }) {
    if (isLoading) {
      return <div className="loading-spinner">Loading...</div>;
    }
    return <Component {...props} />;
  };
}

// Usage
const UserProfileWithLoading = withConditionalLoading(UserProfile);

function App() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUser().then(userData => {
      setUser(userData);
      setLoading(false);
    });
  }, []);
  
  return (
    <UserProfileWithLoading 
      user={user} 
      isLoading={loading} 
    />
  );
}

// HOC for authentication
function withAuth(Component) {
  return function WithAuth({ isAuthenticated, ...props }) {
    if (!isAuthenticated) {
      return <div>Please log in to view this content.</div>;
    }
    return <Component {...props} />;
  };
}

const ProtectedComponent = withAuth(Dashboard);
Best Practices for Conditional Rendering
Keep it Simple

Use the simplest conditional rendering method that meets your needs.

Avoid Deep Nesting

Extract complex conditions into separate variables or functions.

Use Early Returns

Return early from components to avoid nested conditions.

Consider Performance

Be mindful of expensive operations in conditional blocks.

Module 9: Lists and Keys
Rendering Lists in React

Lists are a common pattern in React for displaying collections of data. You can render lists using the JavaScript map() method to transform an array of data into an array of elements.

Basic List Rendering
function NumberList() {
  const numbers = [1, 2, 3, 4, 5];
  
  return (
    <ul>
      {numbers.map((number) => (
        <li key={number.toString()}>
          {number}
        </li>
      ))}
    </ul>
  );
}

// Rendering list of objects
function TodoList() {
  const todos = [
    { id: 1, text: 'Learn React', completed: false },
    { id: 2, text: 'Build a project', completed: true },
    { id: 3, text: 'Deploy to production', completed: false }
  ];
  
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          {todo.text}
          {todo.completed && <span> ✓</span>}
        </li>
      ))}
    </ul>
  );
}
Understanding Keys

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.

Why Keys are Important
  • Helps React identify which items have changed
  • Enables efficient re-rendering
  • Preserves component state
  • Prevents unnecessary DOM manipulations
Choosing the Right Key
// Good: Using unique IDs
function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// Acceptable: Using unique combinations
function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={`${user.id}-${user.name}`}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// Avoid: Using array index (only for static lists)
function NumberList({ numbers }) {
  return (
    <ul>
      {numbers.map((number, index) => (
        <li key={index}>
          {number}
        </li>
      ))}
    </ul>
  );
}

// Bad: Using random values or non-unique values
function BadExample({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={Math.random()}> {/* Don't do this! */}
          {item.name}
        </li>
      ))}
    </ul>
  );
}
Advanced List Patterns
Filtered Lists
function FilteredList({ items, filter }) {
  const filteredItems = items.filter(item => 
    item.name.toLowerCase().includes(filter.toLowerCase())
  );
  
  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}
Sorted Lists
function SortedList({ items }) {
  const sortedItems = [...items].sort((a, b) => 
    a.name.localeCompare(b.name)
  );
  
  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}
Paginated Lists
function PaginatedList({ items, itemsPerPage, currentPage }) {
  const startIndex = (currentPage - 1) * itemsPerPage;
  const endIndex = startIndex + itemsPerPage;
  const currentItems = items.slice(startIndex, endIndex);
  
  return (
    <div>
      <ul>
        {currentItems.map(item => (
          <li key={item.id}>
            {item.name}
          </li>
        ))}
      </ul>
      <Pagination 
        totalItems={items.length}
        itemsPerPage={itemsPerPage}
        currentPage={currentPage}
      />
    </div>
  );
}
Lists with Forms
function EditableList({ items, onChange }) {
  const handleItemChange = (index, field, value) => {
    const newItems = [...items];
    newItems[index] = { ...newItems[index], [field]: value };
    onChange(newItems);
  };
  
  const handleAddItem = () => {
    const newItems = [...items, { id: Date.now(), name: '', quantity: 0 }];
    onChange(newItems);
  };
  
  const handleRemoveItem = (index) => {
    const newItems = items.filter((_, i) => i !== index);
    onChange(newItems);
  };
  
  return (
    <div>
      {items.map((item, index) => (
        <div key={item.id} className="list-item">
          <input
            type="text"
            value={item.name}
            onChange={(e) => handleItemChange(index, 'name', e.target.value)}
            placeholder="Item name"
          />
          <input
            type="number"
            value={item.quantity}
            onChange={(e) => handleItemChange(index, 'quantity', parseInt(e.target.value))}
            placeholder="Quantity"
          />
          <button onClick={() => handleRemoveItem(index)}>Remove</button>
        </div>
      ))}
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
}
Virtual Lists

For large lists, consider using virtualization to render only visible items:

import { FixedSizeList as List } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </List>
  );
}
Best Practices for Lists and Keys
Use Stable Keys

Keys should be stable, predictable, and unique among siblings.

Avoid Index as Key

Don't use array index as key if the list can be reordered or filtered.

Key Scope

Keys only need to be unique among siblings, not globally unique.

Performance

For large lists, consider virtualization or pagination.

Module 10: Forms
Forms in React

Forms in React can be either controlled or uncontrolled. Controlled components are recommended as they provide more control over form data and validation.

Controlled Components

In controlled components, React controls the form input's value through state.

function NameForm() {
  const [value, setValue] = useState('');
  
  const handleChange = (event) => {
    setValue(event.target.value);
  };
  
  const handleSubmit = (event) => {
    event.preventDefault();
    alert('A name was submitted: ' + value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input 
          type="text" 
          value={value}
          onChange={handleChange}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

// Multiple form fields
function RegistrationForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
    confirmPassword: ''
  });
  
  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: value
    }));
  };
  
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form submitted:', formData);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Username:</label>
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label>Password:</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label>Confirm Password:</label>
        <input
          type="password"
          name="confirmPassword"
          value={formData.confirmPassword}
          onChange={handleChange}
          required
        />
      </div>
      <button type="submit">Register</button>
    </form>
  );
}
Textarea Element
function EssayForm() {
  const [value, setValue] = useState('Please write an essay about your favorite DOM element.');
  
  const handleChange = (event) => {
    setValue(event.target.value);
  };
  
  const handleSubmit = (event) => {
    event.preventDefault();
    alert('An essay was submitted: ' + value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Essay:
        <textarea 
          value={value}
          onChange={handleChange}
          rows={10}
          cols={50}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}
Select Element
function FlavorForm() {
  const [value, setValue] = useState('coconut');
  
  const handleChange = (event) => {
    setValue(event.target.value);
  };
  
  const handleSubmit = (event) => {
    event.preventDefault();
    alert('Your favorite flavor is: ' + value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Pick your favorite flavor:
        <select value={value} onChange={handleChange}>
          <option value="grapefruit">Grapefruit</option>
          <option value="lime">Lime</option>
          <option value="coconut">Coconut</option>
          <option value="mango">Mango</option>
        </select>
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

// Multiple select
function MultiSelectForm() {
  const [selectedFruits, setSelectedFruits] = useState([]);
  
  const handleChange = (event) => {
    const selectedOptions = Array.from(event.target.selectedOptions);
    const selectedValues = selectedOptions.map(option => option.value);
    setSelectedFruits(selectedValues);
  };
  
  return (
    <form>
      <label>
        Select fruits:
        <select 
          multiple 
          value={selectedFruits}
          onChange={handleChange}
          size={4}
        >
          <option value="apple">Apple</option>
          <option value="banana">Banana</option>
          <option value="orange">Orange</option>
          <option value="grape">Grape</option>
        </select>
      </label>
      <p>Selected: {selectedFruits.join(', ')}</p>
    </form>
  );
}
Checkbox and Radio Inputs
function CheckboxForm() {
  const [isChecked, setIsChecked] = useState(false);
  const [hobbies, setHobbies] = useState([]);
  
  const handleSingleCheckbox = (event) => {
    setIsChecked(event.target.checked);
  };
  
  const handleMultipleCheckboxes = (event) => {
    const { value, checked } = event.target;
    if (checked) {
      setHobbies([...hobbies, value]);
    } else {
      setHobbies(hobbies.filter(hobby => hobby !== value));
    }
  };
  
  return (
    <form>
      <div>
        <label>
          <input
            type="checkbox"
            checked={isChecked}
            onChange={handleSingleCheckbox}
          />
          I agree to the terms and conditions
        </label>
      </div>
      
      <div>
        <label>Select your hobbies:</label>
        <label>
          <input
            type="checkbox"
            value="reading"
            checked={hobbies.includes('reading')}
            onChange={handleMultipleCheckboxes}
          />
          Reading
        </label>
        <label>
          <input
            type="checkbox"
            value="sports"
            checked={hobbies.includes('sports')}
            onChange={handleMultipleCheckboxes}
          />
          Sports
        </label>
        <label>
          <input
            type="checkbox"
            value="music"
            checked={hobbies.includes('music')}
            onChange={handleMultipleCheckboxes}
          />
          Music
        </label>
      </div>
    </form>
  );
}

function RadioForm() {
  const [selectedOption, setSelectedOption] = useState('option1');
  
  const handleChange = (event) => {
    setSelectedOption(event.target.value);
  };
  
  return (
    <form>
      <label>
        <input
          type="radio"
          value="option1"
          checked={selectedOption === 'option1'}
          onChange={handleChange}
        />
        Option 1
      </label>
      <label>
        <input
          type="radio"
          value="option2"
          checked={selectedOption === 'option2'}
          onChange={handleChange}
        />
        Option 2
      </label>
      <label>
        <input
          type="radio"
          value="option3"
          checked={selectedOption === 'option3'}
          onChange={handleChange}
        />
        Option 3
      </label>
      <p>Selected: {selectedOption}</p>
    </form>
  );
}
File Input
function FileInput() {
  const [file, setFile] = useState(null);
  const [preview, setPreview] = useState('');
  
  const handleFileChange = (event) => {
    const selectedFile = event.target.files[0];
    setFile(selectedFile);
    
    // Create preview for image files
    if (selectedFile && selectedFile.type.startsWith('image/')) {
      const reader = new FileReader();
      reader.onloadend = () => {
        setPreview(reader.result);
      };
      reader.readAsDataURL(selectedFile);
    } else {
      setPreview('');
    }
  };
  
  return (
    <div>
      <input
        type="file"
        onChange={handleFileChange}
        accept="image/*"
      />
      {file && (
        <div>
          <p>Selected file: {file.name}</p>
          <p>File size: {file.size} bytes</p>
          <p>File type: {file.type}</p>
          {preview && (
            <img 
              src={preview} 
              alt="Preview" 
              style={{ maxWidth: '200px', maxHeight: '200px' }}
            />
          )}
        </div>
      )}
    </div>
  );
}
Form Validation
function ValidatedForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    confirmPassword: ''
  });
  
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const validate = () => {
    const newErrors = {};
    
    if (!formData.email) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }
    
    if (!formData.password) {
      newErrors.password = 'Password is required';
    } else if (formData.password.length < 6) {
      newErrors.password = 'Password must be at least 6 characters';
    }
    
    if (!formData.confirmPassword) {
      newErrors.confirmPassword = 'Please confirm your password';
    } else if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = 'Passwords do not match';
    }
    
    return newErrors;
  };
  
  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: value
    }));
    
    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prevErrors => ({
        ...prevErrors,
        [name]: ''
      }));
    }
  };
  
  const handleBlur = (event) => {
    const { name } = event.target;
    setTouched(prevTouched => ({
      ...prevTouched,
      [name]: true
    }));
  };
  
  const handleSubmit = (event) => {
    event.preventDefault();
    const newErrors = validate();
    
    if (Object.keys(newErrors).length === 0) {
      console.log('Form submitted:', formData);
    } else {
      setErrors(newErrors);
      setTouched({
        email: true,
        password: true,
        confirmPassword: true
      });
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          onBlur={handleBlur}
          className={touched.email && errors.email ? 'error' : ''}
        />
        {touched.email && errors.email && (
          <span className="error-message">{errors.email}</span>
        )}
      </div>
      
      <div>
        <label>Password:</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          onBlur={handleBlur}
          className={touched.password && errors.password ? 'error' : ''}
        />
        {touched.password && errors.password && (
          <span className="error-message">{errors.password}</span>
        )}
      </div>
      
      <div>
        <label>Confirm Password:</label>
        <input
          type="password"
          name="confirmPassword"
          value={formData.confirmPassword}
          onChange={handleChange}
          onBlur={handleBlur}
          className={touched.confirmPassword && errors.confirmPassword ? 'error' : ''}
        />
        {touched.confirmPassword && errors.confirmPassword && (
          <span className="error-message">{errors.confirmPassword}</span>
        )}
      </div>
      
      <button type="submit">Submit</button>
    </form>
  );
}
Form Libraries

For complex forms, consider using libraries like Formik or React Hook Form:

// Using React Hook Form
import { useForm } from 'react-hook-form';

function ReactHookFormExample() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  
  const onSubmit = (data) => {
    console.log(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('firstName', { required: 'First name is required' })}
        placeholder="First name"
      />
      {errors.firstName && <span>{errors.firstName.message}</span>}
      
      <input
        {...register('lastName', { required: 'Last name is required' })}
        placeholder="Last name"
      />
      {errors.lastName && <span>{errors.lastName.message}</span>}
      
      <input
        type="email"
        {...register('email', { 
          required: 'Email is required',
          pattern: {
            value: /\S+@\S+\.\S+/,
            message: 'Invalid email address'
          }
        })}
        placeholder="Email"
      />
      {errors.email && <span>{errors.email.message}</span>}
      
      <button type="submit">Submit</button>
    </form>
  );
}
Module 11: Routing with React Router
Introduction to React Router

React Router is the standard routing library for React. It enables navigation between different components in your application, allowing you to build single-page applications with multiple views.

Installation
# Install React Router
npm install react-router-dom

# or with yarn
yarn add react-router-dom
Basic Routing Setup
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

// Page components
function Home() {
  return <h2>Home Page</h2>;
}

function About() {
  return <h2>About Page</h2>;
}

function Contact() {
  return <h2>Contact Page</h2>;
}

// Navigation component
function Navigation() {
  return (
    <nav>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/contact">Contact</Link></li>
      </ul>
    </nav>
  );
}

// Main App component
function App() {
  return (
    <Router>
      <div>
        <Navigation />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </div>
    </Router>
  );
}
Route Parameters
import { useParams } from 'react-router-dom';

function User() {
  let { id } = useParams();
  return <h2>User ID: {id}</h2>;
}

function Product() {
  let { id, category } = useParams();
  return (
    <div>
      <h2>Product Details</h2>
      <p>Category: {category}</p>
      <p>Product ID: {id}</p>
    </div>
  );
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/user/:id" element={<User />} />
        <Route path="/product/:category/:id" element={<Product />} />
      </Routes>
    </Router>
  );
}
Query Parameters
import { useSearchParams } from 'react-router-dom';

function SearchResults() {
  const [searchParams] = useSearchParams();
  const query = searchParams.get('q');
  const page = searchParams.get('page');
  
  return (
    <div>
      <h2>Search Results</h2>
      <p>Query: {query}</p>
      <p>Page: {page || '1'}</p>
    </div>
  );
}

// Navigation with query parameters
function SearchPage() {
  const navigate = useNavigate();
  
  const handleSearch = (query) => {
    navigate(`/search?q=${encodeURIComponent(query)}&page=1`);
  };
  
  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        onKeyPress={(e) => {
          if (e.key === 'Enter') {
            handleSearch(e.target.value);
          }
        }}
      />
    </div>
  );
}
Nested Routes
import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <header>
        <h1>My App</h1>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/dashboard">Dashboard</Link>
        </nav>
      </header>
      <main>
        <Outlet />
      </main>
      <footer>
        <p>© 2023 My App</p>
      </footer>
    </div>
  );
}

function Dashboard() {
  return (
    <div>
      <h2>Dashboard</h2>
      <nav>
        <Link to="overview">Overview</Link>
        <Link to="settings">Settings</Link>
      </nav>
      <Outlet />
    </div>
  );
}

function Overview() {
  return <h3>Dashboard Overview</h3>;
}

function Settings() {
  return <h3>Dashboard Settings</h3>;
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="dashboard" element={<Dashboard />}>
            <Route path="overview" element={<Overview />} />
            <Route path="settings" element={<Settings />} />
          </Route>
        </Route>
      </Routes>
    </Router>
  );
}
Programmatic Navigation
import { useNavigate, useLocation } from 'react-router-dom';

function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  
  const handleLogin = () => {
    // Perform login logic
    // ...
    
    // Redirect to previous page or home
    const from = location.state?.from?.pathname || '/';
    navigate(from, { replace: true });
  };
  
  return (
    <div>
      <h2>Login</h2>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

function ProtectedRoute({ children }) {
  const navigate = useNavigate();
  const location = useLocation();
  const isAuthenticated = false; // Check authentication status
  
  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/login', { state: { from: location } });
    }
  }, [isAuthenticated, navigate, location]);
  
  return isAuthenticated ? children : null;
}
Route Guards and Authentication
function useAuth() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  
  const login = () => {
    setIsAuthenticated(true);
  };
  
  const logout = () => {
    setIsAuthenticated(false);
  };
  
  return { isAuthenticated, login, logout };
}

function PrivateRoute({ children }) {
  const { isAuthenticated } = useAuth();
  const location = useLocation();
  
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route
          path="/dashboard"
          element={
            <PrivateRoute>
              <Dashboard />
            </PrivateRoute>
          }
        />
      </Routes>
    </Router>
  );
}
Custom Routes
// Animated route transitions
import { AnimatePresence, motion } from 'framer-motion';

function AnimatedRoutes() {
  const location = useLocation();
  
  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route
          path="/"
          element={
            <motion.div
              initial={{ opacity: 0, x: -100 }}
              animate={{ opacity: 1, x: 0 }}
              exit={{ opacity: 0, x: 100 }}
            >
              <Home />
            </motion.div>
          }
        />
        <Route
          path="/about"
          element={
            <motion.div
              initial={{ opacity: 0, x: -100 }}
              animate={{ opacity: 1, x: 0 }}
              exit={{ opacity: 0, x: 100 }}
            >
              <About />
            </motion.div>
          }
        />
      </Routes>
    </AnimatePresence>
  );
}

// Lazy loading routes
import { lazy, Suspense } from 'react';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function LazyRoutes() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Suspense>
  );
}
Best Practices
Organize Routes

Keep route definitions organized and consider splitting them into separate files.

Use Nested Routes

Leverage nested routes for better code organization and shared layouts.

Handle 404

Always include a catch-all route for 404 pages.

Lazy Load

Use lazy loading for better performance in large applications.

Module 12: Performance Optimization
React Performance Fundamentals

React applications can become slow as they grow in complexity. Understanding React's rendering behavior and applying optimization techniques is crucial for maintaining smooth user experience.

React.memo

React.memo is a higher-order component that prevents re-rendering if props haven't changed.

import React from 'react';

const ExpensiveComponent = React.memo(function ExpensiveComponent({ data, onUpdate }) {
  console.log('ExpensiveComponent rendered');
  
  // Expensive calculation
  const processedData = data.map(item => ({
    ...item,
    value: item.value * 2
  }));
  
  return (
    <div>
      {processedData.map(item => (
        <ComplexItem key={item.id} item={item} />
      ))}
    </div>
  );
});

// Custom comparison function
const CustomMemoComponent = React.memo(function CustomMemoComponent({ user, settings }) {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>Theme: {settings.theme}</p>
    </div>
  );
}, (prevProps, nextProps) => {
  // Only re-render if user.name or settings.theme changed
  return (
    prevProps.user.name === nextProps.user.name &&
    prevProps.settings.theme === nextProps.settings.theme
  );
});

// Usage
function Parent() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState([{ id: 1, value: 10 }]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveComponent 
        data={data} 
        onUpdate={() => console.log('Update')}
      />
    </div>
  );
}
useMemo Hook

useMemo memoizes expensive calculations and only recomputes when dependencies change.

import { useMemo } from 'react';

function ExpensiveCalculation({ numbers, multiplier }) {
  const expensiveResult = useMemo(() => {
    console.log('Performing expensive calculation...');
    return numbers.reduce((sum, num) => sum + num * multiplier, 0);
  }, [numbers, multiplier]);
  
  return (
    <div>
      <p>Result: {expensiveResult}</p>
    </div>
  );
}

// Memoizing complex objects
function UserList({ users, filter }) {
  const filteredUsers = useMemo(() => {
    console.log('Filtering users...');
    return users.filter(user => 
      user.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [users, filter]);
  
  const userStats = useMemo(() => {
    return {
      total: filteredUsers.length,
      active: filteredUsers.filter(u => u.isActive).length,
      averageAge: filteredUsers.reduce((sum, u) => sum + u.age, 0) / filteredUsers.length || 0
    };
  }, [filteredUsers]);
  
  return (
    <div>
      <div className="stats">
        <p>Total: {userStats.total}</p>
        <p>Active: {userStats.active}</p>
        <p>Average Age: {userStats.averageAge.toFixed(1)}</p>
      </div>
      <ul>
        {filteredUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
useCallback Hook

useCallback memoizes functions and prevents unnecessary re-renders in child components.

import { useCallback } from 'react';

function Button({ onClick, children }) {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
}

const MemoizedButton = React.memo(Button);

function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // Without useCallback - Button re-renders on every Parent render
  const handleClickWithoutCallback = () => {
    setCount(count + 1);
  };
  
  // With useCallback - Button only re-renders when count changes
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);
  
  // With dependencies - re-creates when dependencies change
  const handleReset = useCallback(() => {
    setCount(0);
    setName('');
  }, []);
  
  return (
    <div>
      <MemoizedButton onClick={handleClick}>
        Increment
      </MemoizedButton>
      <MemoizedButton onClick={handleReset}>
        Reset
      </MemoizedButton>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <p>Count: {count}</p>
    </div>
  );
}

// Event handler optimization
function TodoList({ todos, onToggle, onDelete }) {
  const handleToggle = useCallback((id) => {
    onToggle(id);
  }, [onToggle]);
  
  const handleDelete = useCallback((id) => {
    onDelete(id);
  }, [onDelete]);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
}
Code Splitting

Code splitting reduces initial bundle size by loading components on demand.

import { lazy, Suspense } from 'react';

// Lazy loading components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

// Dynamic imports with loading states
function LazyComponent({ componentPath }) {
  const [Component, setComponent] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const loadComponent = async () => {
      try {
        setLoading(true);
        const module = await import(`./components/${componentPath}`);
        setComponent(() => module.default);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    loadComponent();
  }, [componentPath]);
  
  if (loading) return <div>Loading component...</div>;
  if (error) return <div>Error loading component</div>;
  if (!Component) return <div>Component not found</div>;
  
  return <Component />;
}
Virtualization

Virtualization renders only visible items for large lists, improving performance.

import { FixedSizeList as List } from 'react-window';
import { FixedSizeGrid as Grid } from 'react-window';

// Virtual list
function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </List>
  );
}

// Virtual grid
function VirtualGrid({ items }) {
  const Cell = ({ columnIndex, rowIndex, style }) => {
    const item = items[rowIndex * 10 + columnIndex];
    return (
      <div style={style}>
        {item ? item.name : ''}
      </div>
    );
  };
  
  return (
    <Grid
      columnCount={10}
      columnWidth={100}
      height={400}
      rowCount={Math.ceil(items.length / 10)}
      rowHeight={35}
      width={1000}
    >
      {Cell}
    </Grid>
  );
}

// Custom virtual list implementation
function CustomVirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight) + 1,
    items.length - 1
  );
  
  const visibleItems = items.slice(startIndex, endIndex + 1);
  
  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
  };
  
  return (
    <div
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {visibleItems.map((item, index) => (
          <div
            key={startIndex + index}
            style={{
              position: 'absolute',
              top: (startIndex + index) * itemHeight,
              height: itemHeight,
              width: '100%'
            }}
          >
            {item.name}
          </div>
        ))}
      </div>
    </div>
  );
}
State Optimization
// State colocation - keep state close to where it's used
function TodoItem({ todo, onToggle, onDelete }) {
  const [isEditing, setIsEditing] = useState(false);
  const [editText, setEditText] = useState(todo.text);
  
  const handleSave = () => {
    onToggle(todo.id, editText);
    setIsEditing(false);
  };
  
  return (
    <div>
      {isEditing ? (
        <input
          value={editText}
          onChange={(e) => setEditText(e.target.value)}
          onBlur={handleSave}
          onKeyPress={(e) => e.key === 'Enter' && handleSave()}
        />
      ) : (
        <span onClick={() => setIsEditing(true)}>
          {todo.text}
        </span>
      )}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </div>
  );
}

// State normalization for complex data
function useNormalizedState(initialData) {
  const [entities, setEntities] = useState({});
  const [ids, setIds] = useState([]);
  
  const normalize = (data) => {
    const newEntities = {};
    const newIds = [];
    
    data.forEach(item => {
      newEntities[item.id] = item;
      newIds.push(item.id);
    });
    
    setEntities(newEntities);
    setIds(newIds);
  };
  
  const updateEntity = (id, updates) => {
    setEntities(prev => ({
      ...prev,
      [id]: { ...prev[id], ...updates }
    }));
  };
  
  const deleteEntity = (id) => {
    setEntities(prev => {
      const newEntities = { ...prev };
      delete newEntities[id];
      return newEntities;
    });
    setIds(prev => prev.filter(existingId => existingId !== id));
  };
  
  return {
    entities,
    ids,
    normalize,
    updateEntity,
    deleteEntity
  };
}
Performance Monitoring
// React DevTools Profiler
import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration) {
  console.log('Component:', id);
  console.log('Phase:', phase);
  console.log('Duration:', actualDuration);
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}

// Custom performance hook
function usePerformanceMonitor(componentName) {
  const renderCount = useRef(0);
  const lastRenderTime = useRef(Date.now());
  
  useEffect(() => {
    renderCount.current += 1;
    const now = Date.now();
    const timeSinceLastRender = now - lastRenderTime.current;
    
    console.log(`${componentName} rendered ${renderCount.current} times`);
    console.log(`Time since last render: ${timeSinceLastRender}ms`);
    
    lastRenderTime.current = now;
  });
  
  return renderCount.current;
}

// Usage
function MyComponent() {
  const renderCount = usePerformanceMonitor('MyComponent');
  
  return (
    <div>
      <p>Render count: {renderCount}</p>
      {/* Component content */}
    </div>
  );
}
Performance Best Practices
Avoid Unnecessary Renders

Use React.memo, useMemo, and useCallback to prevent unnecessary re-renders.

Optimize State Updates

Batch state updates and use functional updates when possible.

Virtualize Large Lists

Use virtualization for lists with many items.

Code Split

Split code into smaller chunks and load on demand.

Monitor Performance

Use React DevTools Profiler to identify performance bottlenecks.

Module 13: Testing React Applications
Introduction to React Testing

Testing is crucial for building reliable React applications. It helps catch bugs early, ensures code quality, and makes refactoring safer. React applications can be tested at multiple levels: unit tests, integration tests, and end-to-end tests.

Setting Up Testing Environment
# Install testing libraries
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event jest

# Install additional testing utilities
npm install --save-dev @testing-library/jest-environment-jsdom msw

# For TypeScript
npm install --save-dev @types/jest
Component Testing
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event';

// Component to test
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p data-testid="count">Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

// Test file
describe('Counter Component', () => {
  test('renders initial count', () => {
    render(<Counter />);
    
    expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
  });
  
  test('increments count when increment button is clicked', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    
    const incrementButton = screen.getByText('Increment');
    await user.click(incrementButton);
    
    expect(screen.getByTestId('count')).toHaveTextContent('Count: 1');
  });
  
  test('decrements count when decrement button is clicked', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    
    const decrementButton = screen.getByText('Decrement');
    await user.click(decrementButton);
    
    expect(screen.getByTestId('count')).toHaveTextContent('Count: -1');
  });
  
  test('multiple clicks update count correctly', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    
    const incrementButton = screen.getByText('Increment');
    await user.click(incrementButton);
    await user.click(incrementButton);
    await user.click(incrementButton);
    
    expect(screen.getByTestId('count')).toHaveTextContent('Count: 3');
  });
});
Testing Forms
// Form component
function LoginForm({ onSubmit }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (!email || !password) {
      setError('Please fill in all fields');
      return;
    }
    
    setError('');
    onSubmit({ email, password });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          data-testid="email-input"
        />
      </div>
      <div>
        <label>Password:</label>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          data-testid="password-input"
        />
      </div>
      {error && <div data-testid="error">{error}</div>}
      <button type="submit" data-testid="submit-button">Login</button>
    </form>
  );
}

// Test file
describe('LoginForm Component', () => {
  const mockSubmit = jest.fn();
  
  beforeEach(() => {
    mockSubmit.mockClear();
  });
  
  test('renders form elements', () => {
    render(<LoginForm onSubmit={mockSubmit} />);
    
    expect(screen.getByTestId('email-input')).toBeInTheDocument();
    expect(screen.getByTestId('password-input')).toBeInTheDocument();
    expect(screen.getByTestId('submit-button')).toBeInTheDocument();
  });
  
  test('shows error when submitting empty form', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockSubmit} />);
    
    const submitButton = screen.getByTestId('submit-button');
    await user.click(submitButton);
    
    expect(screen.getByTestId('error')).toHaveTextContent('Please fill in all fields');
    expect(mockSubmit).not.toHaveBeenCalled();
  });
  
  test('submits form with valid data', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockSubmit} />);
    
    const emailInput = screen.getByTestId('email-input');
    const passwordInput = screen.getByTestId('password-input');
    const submitButton = screen.getByTestId('submit-button');
    
    await user.type(emailInput, 'test@example.com');
    await user.type(passwordInput, 'password123');
    await user.click(submitButton);
    
    expect(screen.queryByTestId('error')).not.toBeInTheDocument();
    expect(mockSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123'
    });
  });
  
  test('clears error when user starts typing', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockSubmit} />);
    
    const submitButton = screen.getByTestId('submit-button');
    const emailInput = screen.getByTestId('email-input');
    
    await user.click(submitButton);
    expect(screen.getByTestId('error')).toBeInTheDocument();
    
    await user.type(emailInput, 'test@example.com');
    expect(screen.queryByTestId('error')).not.toBeInTheDocument();
  });
});
Testing Async Components
// Async component
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch user');
        const userData = await response.json();
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]);
  
  if (loading) return <div data-testid="loading">Loading...</div>;
  if (error) return <div data-testid="error">Error: {error}</div>;
  if (!user) return <div data-testid="no-user">No user found</div>;
  
  return (
    <div data-testid="user-profile">
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// Test file with mocking
describe('UserProfile Component', () => {
  const mockFetch = jest.fn();
  
  beforeEach(() => {
    mockFetch.mockClear();
    global.fetch = mockFetch;
  });
  
  afterEach(() => {
    delete global.fetch;
  });
  
  test('shows loading state initially', () => {
    render(<UserProfile userId="1" />);
    
    expect(screen.getByTestId('loading')).toBeInTheDocument();
    expect(screen.queryByTestId('user-profile')).not.toBeInTheDocument();
  });
  
  test('displays user data after successful fetch', async () => {
    const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com' };
    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockUser
    });
    
    render(<UserProfile userId="1" />);
    
    expect(screen.getByTestId('loading')).toBeInTheDocument();
    
    await waitFor(() => {
      expect(screen.getByTestId('user-profile')).toBeInTheDocument();
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('john@example.com')).toBeInTheDocument();
    });
    
    expect(mockFetch).toHaveBeenCalledWith('/api/users/1');
  });
  
  test('displays error message when fetch fails', async () => {
    mockFetch.mockResolvedValueOnce({
      ok: false,
      statusText: 'Not Found'
    });
    
    render(<UserProfile userId="999" />);
    
    await waitFor(() => {
      expect(screen.getByTestId('error')).toBeInTheDocument();
      expect(screen.getByText(/Failed to fetch user/)).toBeInTheDocument();
    });
  });
  
  test('refetches when userId changes', async () => {
    const mockUser1 = { id: 1, name: 'John Doe', email: 'john@example.com' };
    const mockUser2 = { id: 2, name: 'Jane Smith', email: 'jane@example.com' };
    
    mockFetch
      .mockResolvedValueOnce({ ok: true, json: async () => mockUser1 })
      .mockResolvedValueOnce({ ok: true, json: async () => mockUser2 });
    
    const { rerender } = render(<UserProfile userId="1" />);
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
    });
    
    rerender(<UserProfile userId="2" />);
    
    expect(screen.getByTestId('loading')).toBeInTheDocument();
    
    await waitFor(() => {
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
    
    expect(mockFetch).toHaveBeenCalledTimes(2);
  });
});
Testing Custom Hooks
import { renderHook, act } from '@testing-library/react';

// Custom hook
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialValue);
  
  return { count, increment, decrement, reset };
}

// Test file
describe('useCounter Hook', () => {
  test('returns initial count', () => {
    const { result } = renderHook(() => useCounter());
    
    expect(result.current.count).toBe(0);
  });
  
  test('returns custom initial count', () => {
    const { result } = renderHook(() => useCounter(5));
    
    expect(result.current.count).toBe(5);
  });
  
  test('increments count', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  test('decrements count', () => {
    const { result } = renderHook(() => useCounter(10));
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(9);
  });
  
  test('resets count to initial value', () => {
    const { result } = renderHook(() => useCounter(5));
    
    act(() => {
      result.current.increment();
      result.current.increment();
    });
    
    expect(result.current.count).toBe(7);
    
    act(() => {
      result.current.reset();
    });
    
    expect(result.current.count).toBe(5);
  });
  
  test('handles multiple updates', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
      result.current.increment();
      result.current.decrement();
      result.current.increment();
    });
    
    expect(result.current.count).toBe(2);
  });
});
Mocking API Calls
import { setupServer } from 'msw/node';
import { rest } from 'msw';

// Mock server setup
const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
      ])
    );
  }),
  rest.post('/api/users', (req, res, ctx) => {
    return res(
      ctx.status(201),
      ctx.json({ id: 3, name: 'New User', email: 'new@example.com' })
    );
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

// Component that uses API
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch('/api/users')
      .then(response => response.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      });
  }, []);
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// Test file
describe('UserList with MSW', () => {
  test('displays users from API', async () => {
    render(<UserList />);
    
    expect(screen.getByText('Loading...')).toBeInTheDocument();
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });
  
  test('handles API error', async () => {
    server.use(
      rest.get('/api/users', (req, res, ctx) => {
        return res(ctx.status(500));
      })
    );
    
    render(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText(/Error/)).toBeInTheDocument();
    });
  });
});
Integration Testing
// Integration test for user flow
describe('User Registration Flow', () => {
  test('user can register and see profile', async () => {
    const user = userEvent.setup();
    
    render(<App />);
    
    // Navigate to registration
    const registerLink = screen.getByText('Register');
    await user.click(registerLink);
    
    // Fill registration form
    const nameInput = screen.getByLabelText('Name');
    const emailInput = screen.getByLabelText('Email');
    const passwordInput = screen.getByLabelText('Password');
    const submitButton = screen.getByText('Register');
    
    await user.type(nameInput, 'John Doe');
    await user.type(emailInput, 'john@example.com');
    await user.type(passwordInput, 'password123');
    await user.click(submitButton);
    
    // Verify registration success
    await waitFor(() => {
      expect(screen.getByText('Registration successful!')).toBeInTheDocument();
    });
    
    // Verify redirect to profile
    await waitFor(() => {
      expect(screen.getByText('Welcome, John Doe')).toBeInTheDocument();
      expect(screen.getByText('john@example.com')).toBeInTheDocument();
    });
  });
});
Testing Best Practices
Test Behavior, Not Implementation

Focus on what the component does, not how it does it.

Use Meaningful Queries

Use getByRole, getByLabelText, and other accessible queries.

Mock External Dependencies

Mock API calls, timers, and other external dependencies.

Test Edge Cases

Test error states, empty states, and boundary conditions.

Keep Tests Simple

Each test should focus on one specific behavior.

Module 14: React Best Practices
Component Architecture

Building maintainable React applications requires following established patterns and best practices for component design and organization.

Single Responsibility Principle
// Good: Component with single responsibility
function UserAvatar({ user, size }) {
  return (
    <img
      src={user.avatarUrl}
      alt={user.name}
      width={size}
      height={size}
      className="user-avatar"
    />
  );
}

function UserInfo({ user }) {
  return (
    <div className="user-info">
      <UserAvatar user={user} size={50} />
      <div className="user-details">
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
    </div>
  );
}

// Bad: Component doing too much
function UserCard({ user }) {
  const [isEditing, setIsEditing] = useState(false);
  const [editName, setEditName] = useState(user.name);
  
  const handleSave = async () => {
    await fetch(`/api/users/${user.id}`, {
      method: 'PUT',
      body: JSON.stringify({ name: editName })
    });
    setIsEditing(false);
  };
  
  return (
    <div>
      <img src={user.avatarUrl} alt={user.name} />
      {isEditing ? (
        <input
          value={editName}
          onChange={(e) => setEditName(e.target.value)}
        />
      ) : (
        <h3>{user.name}</h3>
      )}
      <p>{user.email}</p>
      <button onClick={() => setIsEditing(true)}>Edit</button>
      <button onClick={handleSave}>Save</button>
    </div>
  );
}
Composition over Inheritance
// Good: Using composition
function Card({ children, className, ...props }) {
  return (
    <div className={`card ${className}`} {...props}>
      {children}
    </div>
  );
}

function CardHeader({ children }) {
  return <div className="card-header">{children}</div>;
}

function CardBody({ children }) {
  return <div className="card-body">{children}</div>;
}

function CardFooter({ children }) {
  return <div className="card-footer">{children}</div>;
}

// Usage
function ProductCard({ product }) {
  return (
    <Card className="product-card">
      <CardHeader>
        <h3>{product.name}</h3>
      </CardHeader>
      <CardBody>
        <p>{product.description}</p>
        <p>${product.price}</p>
      </CardBody>
      <CardFooter>
        <button>Add to Cart</button>
      </CardFooter>
    </Card>
  );
}

// Bad: Using inheritance (not recommended in React)
class BaseCard extends React.Component {
  render() {
    return (
      <div className={`card ${this.props.className}`}>
        {this.props.children}
      </div>
    );
  }
}

class ProductCard extends BaseCard {
  render() {
    return (
      <super>
        <h3>{this.props.product.name}</h3>
        <p>{this.props.product.description}</p>
      </super>
    );
  }
}
State Management Patterns
// Good: Lifting state up when needed
function Parent() {
  const [sharedState, setSharedState] = useState('initial');
  
  return (
    <div>
      <ChildA value={sharedState} onChange={setSharedState} />
      <ChildB value={sharedState} />
    </div>
  );
}

// Good: Using context for truly global state
const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Good: Keeping state local when possible
function TodoItem({ todo, onToggle, onDelete }) {
  const [isEditing, setIsEditing] = useState(false); // Local state
  
  return (
    <div>
      {isEditing ? (
        <TodoEditForm
          todo={todo}
          onSave={() => setIsEditing(false)}
        />
      ) : (
        <TodoDisplay
          todo={todo}
          onEdit={() => setIsEditing(true)}
          onToggle={onToggle}
          onDelete={onDelete}
        />
      )}
    </div>
  );
}
Prop Drilling Solutions
// Problem: Prop drilling
function App() {
  const [user, setUser] = useState(null);
  
  return (
    <div>
      <Header user={user} />
      <Main>
        <Dashboard user={user} />
        <Sidebar>
          <UserProfile user={user} />
        </Sidebar>
      </Main>
    </div>
  );
}

// Solution 1: Context API
const UserContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <div>
        <Header />
        <Main>
          <Dashboard />
          <Sidebar>
            <UserProfile />
          </Sidebar>
        </Main>
      </div>
    </UserContext.Provider>
  );
}

// Solution 2: Component composition
function App() {
  const [user, setUser] = useState(null);
  
  return (
    <div>
      <Header />
      <Main>
        <UserProvider user={user}>
          <Dashboard />
          <Sidebar>
            <UserProfile />
          </Sidebar>
        </UserProvider>
      </Main>
    </div>
  );
}

function UserProvider({ user, children }) {
  return (
    <div>
      {React.Children.map(children, child => 
        React.cloneElement(child, { user })
      )}
    </div>
  );
}
Custom Hooks for Logic Reuse
// Good: Custom hook for API calls
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useApi(`/api/users/${userId}`);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user?.name}</div>;
}

// Good: Custom hook for local storage
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}
Performance Best Practices
// Good: Using React.memo for expensive components
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      computed: expensiveCalculation(item)
    }));
  }, [data]);
  
  return (
    <div>
      {processedData.map(item => (
        <ComplexItem key={item.id} item={item} />
      ))}
    </div>
  );
});

// Good: Using useCallback for stable function references
function Parent({ items }) {
  const [selectedId, setSelectedId] = useState(null);
  
  const handleSelect = useCallback((id) => {
    setSelectedId(id);
  }, []);
  
  return (
    <div>
      {items.map(item => (
        <Item
          key={item.id}
          item={item}
          onSelect={handleSelect}
          isSelected={selectedId === item.id}
        />
      ))}
    </div>
  );
}

// Good: Code splitting for large components
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  const [showHeavy, setShowHeavy] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowHeavy(true)}>
        Load Heavy Component
      </button>
      {showHeavy && (
        <Suspense fallback={<div>Loading...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}
Code Organization
// Good: Feature-based organization
src/
├── components/
│   ├── common/
│   │   ├── Button/
│   │   │   ├── Button.jsx
│   │   │   ├── Button.module.css
│   │   │   ├── Button.test.jsx
│   │   │   └── index.js
│   │   └── Input/
│   └── features/
│       ├── UserProfile/
│       │   ├── UserProfile.jsx
│       │   ├── UserProfile.module.css
│       │   ├── UserProfile.test.jsx
│       │   └── index.js
│       └── TodoList/
├── hooks/
│   ├── useApi.js
│   ├── useLocalStorage.js
│   └── index.js
├── context/
│   ├── ThemeContext.js
│   └── index.js
├── utils/
│   ├── helpers.js
│   └── constants.js
└── services/
    ├── api.js
    └── auth.js

// Good: Barrel exports
// components/common/Button/index.js
export { default } from './Button';
export { ButtonSizes } from './Button.constants';

// Good: Index files for clean imports
// components/index.js
export { default as Button } from './common/Button';
export { default as Input } from './common/Input';
export { default as UserProfile } from './features/UserProfile';
Error Handling
// Good: Error boundaries for class components
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // Send error to logging service
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong.</h2>
          <details>
            {this.state.error && this.state.error.toString()}
          </details>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Good: Error handling with hooks
function useErrorHandler() {
  const [error, setError] = useState(null);
  
  const resetError = () => setError(null);
  
  const catchError = (error) => {
    setError(error);
  };
  
  useEffect(() => {
    if (error) {
      throw error;
    }
  }, [error]);
  
  return { catchError, resetError };
}

function ErrorBoundary({ children }) {
  const { catchError, resetError } = useErrorHandler();
  
  return (
    <div>
      {children}
      <button onClick={resetError}>Reset Error</button>
    </div>
  );
}
Security Best Practices
Sanitize User Input

Always sanitize user input to prevent XSS attacks.

Use HTTPS

Always use HTTPS in production to encrypt data in transit.

Validate Data

Validate all data on both client and server side.

Secure API Keys

Never expose API keys or sensitive data in client-side code.

Use CSP Headers

Implement Content Security Policy headers for additional security.

Module 15: Advanced React Patterns
Compound Components

Compound components are a pattern where multiple components work together to share implicit state through a context.

import React, { createContext, useContext } from 'react';

const ToggleContext = createContext();

function Toggle({ children, onToggle }) {
  const [on, setOn] = useState(false);
  
  const toggle = () => {
    const newOn = !on;
    setOn(newOn);
    onToggle?.(newOn);
  };
  
  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

function useToggle() {
  const context = useContext(ToggleContext);
  if (!context) {
    throw new Error('useToggle must be used within a Toggle');
  }
  return context;
}

// Compound components
Toggle.On = function ToggleOn({ children }) {
  const { on } = useToggle();
  return on ? children : null;
};

Toggle.Off = function ToggleOff({ children }) {
  const { on } = useToggle();
  return on ? null : children;
};

Toggle.Button = function ToggleButton(props) {
  const { on, toggle } = useToggle();
  return (
    <button onClick={toggle} {...props}>
      {on ? 'Turn Off' : 'Turn On'}
    </button>
  );
};

// Usage
function App() {
  return (
    <Toggle onToggle={(on) => console.log('Toggle is:', on)}>
      <Toggle.On>The button is ON</Toggle.On>
      <Toggle.Off>The button is OFF</Toggle.Off>
      <Toggle.Button />
    </Toggle>
  );
}

// More complex example: Menu component
const MenuContext = createContext();

function Menu({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <MenuContext.Provider value={{ isOpen, setIsOpen }}>
      <div className="menu">{children}</div>
    </MenuContext.Provider>
  );
}

function useMenu() {
  const context = useContext(MenuContext);
  if (!context) {
    throw new Error('useMenu must be used within a Menu');
  }
  return context;
}

Menu.Button = function MenuButton({ children }) {
  const { isOpen, setIsOpen } = useMenu();
  
  return (
    <button onClick={() => setIsOpen(!isOpen)}>
      {children} {isOpen ? '▲' : '▼'}
    </button>
  );
};

Menu.List = function MenuList({ children }) {
  const { isOpen } = useMenu();
  
  return isOpen ? <ul className="menu-list">{children}</ul> : null;
};

Menu.Item = function MenuItem({ children, onClick }) {
  const { setIsOpen } = useMenu();
  
  const handleClick = () => {
    onClick?.();
    setIsOpen(false);
  };
  
  return (
    <li onClick={handleClick}>
      {children}
    </li>
  );
};

// Usage
function DropdownMenu() {
  return (
    <Menu>
      <Menu.Button>Options</Menu.Button>
      <Menu.List>
        <Menu.Item onClick={() => console.log('Edit')}>Edit</Menu.Item>
        <Menu.Item onClick={() => console.log('Delete')}>Delete</Menu.Item>
        <Menu.Item onClick={() => console.log('Share')}>Share</Menu.Item>
      </Menu.List>
    </Menu>
  );
}
Render Props Pattern

Render props is a technique for sharing code between components using a prop whose value is a function.

// Mouse tracker with render prop
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  const handleMouseMove = (event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  };
  
  return (
    <div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
      {render(position)}
    </div>
  );
}

// Usage
function App() {
  return (
    <MouseTracker
      render={({ x, y }) => (
        <h1>
          Mouse position: ({x}, {y})
        </h1>
      )}
    />
  );
}

// Using children as render prop
function MouseTracker({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  const handleMouseMove = (event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  };
  
  return (
    <div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
      {children(position)}
    </div>
  );
}

// Usage
function App() {
  return (
    <MouseTracker>
      {({ x, y }) => (
        <h1>
          Mouse position: ({x}, {y})
        </h1>
      )}
    </MouseTracker>
  );
}

// More complex example: Data provider
function DataProvider({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return render({ data, loading, error });
}

// Usage
function UserList() {
  return (
    <DataProvider
      url="/api/users"
      render={({ data, loading, error }) => {
        if (loading) return <div>Loading...</div>;
        if (error) return <div>Error: {error}</div>;
        if (!data) return <div>No data</div>;
        
        return (
          <ul>
            {data.map(user => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
        );
      }}
    />
  );
}
Higher-Order Components (HOC)

HOCs are functions that take a component and return a new component with additional props or behavior.

// Simple HOC for adding props
function withExtraProps(Component) {
  return function WithExtraProps(props) {
    const extraProps = {
      timestamp: new Date().toISOString(),
      version: '1.0.0'
    };
    
    return <Component {...props} {...extraProps} />;
  };
}

// HOC for loading state
function withLoading(Component) {
  return function WithLoading({ isLoading, ...props }) {
    if (isLoading) {
      return <div className="loading-spinner">Loading...</div>;
    }
    return <Component {...props} />;
  };
}

// HOC for error handling
function withErrorBoundary(Component) {
  return class WithErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false, error: null };
    }
    
    static getDerivedStateFromError(error) {
      return { hasError: true, error };
    }
    
    componentDidCatch(error, errorInfo) {
      console.error('Error in component:', error, errorInfo);
    }
    
    render() {
      if (this.state.hasError) {
        return (
          <div className="error-boundary">
            <h2>Something went wrong.</h2>
            <p>{this.state.error?.message}</p>
          </div>
        );
      }
      
      return <Component {...this.props} />;
    }
  };
}

// HOC for authentication
function withAuth(Component) {
  return function WithAuth({ isAuthenticated, ...props }) {
    if (!isAuthenticated) {
      return <div>Please log in to view this content.</div>;
    }
    return <Component {...props} />;
  };
}

// Combining HOCs
function enhance(Component) {
  return withErrorBoundary(
    withAuth(
      withLoading(Component)
    )
  );
}

// Usage
const EnhancedUserProfile = enhance(UserProfile);

function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// HOC with configuration
function withLogger(Component, options = {}) {
  const { logProps = false, logRender = false } = options;
  
  return function WithLogger(props) {
    if (logProps) {
      console.log('Props:', props);
    }
    
    if (logRender) {
      console.log('Rendering:', Component.name);
    }
    
    return <Component {...props} />;
  };
}

// Usage
const LoggedComponent = withLogger(MyComponent, {
  logProps: true,
  logRender: true
});
Custom Hooks Pattern

Custom hooks allow you to extract component logic into reusable functions.

// Hook for managing window size
function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return windowSize;
}

// Hook for managing online/offline status
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  return isOnline;
}

// Hook for managing local storage with sync
function useStorageSync(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  // Sync with storage changes from other tabs
  useEffect(() => {
    const handleStorageChange = (e) => {
      if (e.key === key) {
        setStoredValue(e.newValue ? JSON.parse(e.newValue) : initialValue);
      }
    };
    
    window.addEventListener('storage', handleStorageChange);
    return () => window.removeEventListener('storage', handleStorageChange);
  }, [key, initialValue]);
  
  return [storedValue, setValue];
}

// Hook for debouncing values
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  
  return debouncedValue;
}

// Hook for async operations
function useAsync(asyncFunction, dependencies = []) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const execute = useCallback(async (...args) => {
    try {
      setLoading(true);
      setError(null);
      const result = await asyncFunction(...args);
      setData(result);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [asyncFunction]);
  
  useEffect(() => {
    execute();
  }, dependencies);
  
  return { data, loading, error, execute };
}

// Usage examples
function ResponsiveComponent() {
  const { width, height } = useWindowSize();
  
  return (
    <div>
      <p>Window width: {width}px</p>
      <p>Window height: {height}px</p>
      {width < 768 ? <MobileView /> : <DesktopView />}
    </div>
  );
}

function OnlineStatusIndicator() {
  const isOnline = useOnlineStatus();
  
  return (
    <div className={`status ${isOnline ? 'online' : 'offline'}`}>
      {isOnline ? '🟢 Online' : '🔴 Offline'}
    </div>
  );
}

function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  
  const { data, loading, error } = useAsync(
    async (term) => {
      if (!term) return [];
      const response = await fetch(`/api/search?q=${term}`);
      return response.json();
    },
    [debouncedSearchTerm]
  );
  
  return (
    <div>
      <input
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search..."
      />
      {loading && <div>Searching...</div>}
      {error && <div>Error: {error.message}</div>}
      {data && (
        <ul>
          {data.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}
State Reducer Pattern

Using useReducer for complex state logic makes state updates more predictable and easier to test.

// Complex form state with reducer
const formReducer = (state, action) => {
  switch (action.type) {
    case 'SET_FIELD':
      return {
        ...state,
        [action.field]: action.value,
        errors: {
          ...state.errors,
          [action.field]: null
        }
      };
    
    case 'SET_ERROR':
      return {
        ...state,
        errors: {
          ...state.errors,
          [action.field]: action.error
        }
      };
    
    case 'SET_ERRORS':
      return {
        ...state,
        errors: action.errors
      };
    
    case 'RESET_FORM':
      return action.initialState;
    
    case 'SUBMIT_START':
      return {
        ...state,
        isSubmitting: true,
        submitError: null
      };
    
    case 'SUBMIT_SUCCESS':
      return {
        ...state,
        isSubmitting: false,
        submitSuccess: true
      };
    
    case 'SUBMIT_ERROR':
      return {
        ...state,
        isSubmitting: false,
        submitError: action.error
      };
    
    default:
      return state;
  }
};

function useForm(initialState, validationSchema, onSubmit) {
  const [state, dispatch] = useReducer(formReducer, initialState);
  
  const setField = (field, value) => {
    dispatch({ type: 'SET_FIELD', field, value });
    
    // Validate field if schema provided
    if (validationSchema?.[field]) {
      const error = validateField(value, validationSchema[field]);
      if (error) {
        dispatch({ type: 'SET_ERROR', field, error });
      }
    }
  };
  
  const setError = (field, error) => {
    dispatch({ type: 'SET_ERROR', field, error });
  };
  
  const setErrors = (errors) => {
    dispatch({ type: 'SET_ERRORS', errors });
  };
  
  const validateForm = () => {
    if (!validationSchema) return true;
    
    const errors = {};
    let isValid = true;
    
    Object.keys(validationSchema).forEach(field => {
      const error = validateField(state[field], validationSchema[field]);
      if (error) {
        errors[field] = error;
        isValid = false;
      }
    });
    
    setErrors(errors);
    return isValid;
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    if (!validateForm()) return;
    
    dispatch({ type: 'SUBMIT_START' });
    
    try {
      await onSubmit(state);
      dispatch({ type: 'SUBMIT_SUCCESS' });
    } catch (error) {
      dispatch({ type: 'SUBMIT_ERROR', error: error.message });
    }
  };
  
  const resetForm = () => {
    dispatch({ type: 'RESET_FORM', initialState });
  };
  
  return {
    ...state,
    setField,
    setError,
    setErrors,
    handleSubmit,
    resetForm,
    validateForm
  };
}

// Usage
function RegistrationForm() {
  const initialState = {
    username: '',
    email: '',
    password: '',
    confirmPassword: '',
    errors: {},
    isSubmitting: false,
    submitSuccess: false,
    submitError: null
  };
  
  const validationSchema = {
    username: {
      required: true,
      minLength: 3
    },
    email: {
      required: true,
      pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    },
    password: {
      required: true,
      minLength: 6
    },
    confirmPassword: {
      required: true,
      validate: (value, allValues) => value === allValues.password
    }
  };
  
  const {
    values,
    errors,
    isSubmitting,
    submitSuccess,
    submitError,
    setField,
    handleSubmit,
    resetForm
  } = useForm(initialState, validationSchema, async (values) => {
    const response = await fetch('/api/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(values)
    });
    
    if (!response.ok) throw new Error('Registration failed');
    
    return response.json();
  });
  
  if (submitSuccess) {
    return (
      <div>
        <h2>Registration successful!</h2>
        <button onClick={resetForm}>Register another user</button>
      </div>
    );
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Username:</label>
        <input
          value={values.username}
          onChange={(e) => setField('username', e.target.value)}
        />
        {errors.username && <span className="error">{errors.username}</span>}
      </div>
      
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={values.email}
          onChange={(e) => setField('email', e.target.value)}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <label>Password:</label>
        <input
          type="password"
          value={values.password}
          onChange={(e) => setField('password', e.target.value)}
        />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      
      <div>
        <label>Confirm Password:</label>
        <input
          type="password"
          value={values.confirmPassword}
          onChange={(e) => setField('confirmPassword', e.target.value)}
        />
        {errors.confirmPassword && <span className="error">{errors.confirmPassword}</span>}
      </div>
      
      {submitError && <div className="error">{submitError}</div>}
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Registering...' : 'Register'}
      </button>
    </form>
  );
}
Provider Pattern

The Provider pattern uses React's Context API to provide values to a tree of components.

// Theme provider
const ThemeContext = createContext();

function ThemeProvider({ children, theme = 'light' }) {
  const [currentTheme, setCurrentTheme] = useState(theme);
  
  const toggleTheme = () => {
    setCurrentTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const themeValues = useMemo(() => ({
    theme: currentTheme,
    toggleTheme,
    colors: currentTheme === 'light' ? {
      background: '#ffffff',
      text: '#000000',
      primary: '#007bff'
    } : {
      background: '#1a1a1a',
      text: '#ffffff',
      primary: '#0d6efd'
    }
  }), [currentTheme]);
  
  return (
    <ThemeContext.Provider value={themeValues}>
      {children}
    </ThemeContext.Provider>
  );
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

// Auth provider
const AuthContext = createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Check for existing session
    const token = localStorage.getItem('token');
    if (token) {
      // Validate token and get user info
      validateToken(token).then(userData => {
        setUser(userData);
        setLoading(false);
      }).catch(() => {
        localStorage.removeItem('token');
        setLoading(false);
      });
    } else {
      setLoading(false);
    }
  }, []);
  
  const login = async (credentials) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(credentials)
    });
    
    if (!response.ok) throw new Error('Login failed');
    
    const { token, user } = await response.json();
    localStorage.setItem('token', token);
    setUser(user);
  };
  
  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };
  
  const value = {
    user,
    login,
    logout,
    isAuthenticated: !!user,
    loading
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}

// Combined providers
function AppProviders({ children }) {
  return (
    <AuthProvider>
      <ThemeProvider>
        <Router>
          {children}
        </Router>
      </ThemeProvider>
    </AuthProvider>
  );
}

// Usage
function App() {
  return (
    <AppProviders>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/profile" element={<ProtectedRoute><Profile /></ProtectedRoute>} />
      </Routes>
    </AppProviders>
  );
}

function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth();
  const location = useLocation();
  
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}
Module 16: Error Boundaries
Understanding Error Boundaries

Error boundaries are React components that catch JavaScript errors in their child component tree, log those errors, and display a fallback UI instead of the crashed component tree. They prevent the entire app from crashing due to errors in individual components.

Class Component Error Boundaries
import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null, 
      errorInfo: null 
    };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Log the error to an error reporting service
    console.error('Error caught by boundary:', error, errorInfo);
    
    // You can also send the error to a service like Sentry
    // Sentry.captureException(error, { contexts: { react: { componentStack: errorInfo.componentStack } } });
    
    this.setState({
      error: error,
      errorInfo: errorInfo
    });
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return (
        <div className="error-boundary">
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            <summary>Error details</summary>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
          <button onClick={() => this.setState({ hasError: false, error: null, errorInfo: null })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

// Component that throws an error
function BuggyComponent() {
  const [shouldThrow, setShouldThrow] = useState(false);
  
  if (shouldThrow) {
    throw new Error('I crashed!');
  }
  
  return (
    <div>
      <p>I'm working fine!</p>
      <button onClick={() => setShouldThrow(true)}>
        Break me
      </button>
    </div>
  );
}
Functional Error Boundary Pattern

While React doesn't support functional error boundaries directly, we can create a pattern using hooks:

import React, { useState, useEffect } from 'react';

function useErrorHandler() {
  const [error, setError] = useState(null);
  
  const resetError = () => setError(null);
  
  const catchError = (error) => {
    setError(error);
  };
  
  useEffect(() => {
    if (error) {
      throw error;
    }
  }, [error]);
  
  return { catchError, resetError };
}

function ErrorBoundary({ children, fallback }) {
  const { catchError, resetError } = useErrorHandler();
  
  return (
    <div>
      {children}
      {error && (
        <div className="error-boundary">
          {fallback || <h2>Something went wrong.</h2>}
          <button onClick={resetError}>Try again</button>
        </div>
      )}
    </div>
  );
}

// Usage
function MyComponent() {
  const { catchError } = useErrorHandler();
  
  const handleClick = () => {
    try {
      // Code that might throw an error
      throw new Error('Something went wrong!');
    } catch (error) {
      catchError(error);
    }
  };
  
  return (
    <div>
      <button onClick={handleClick}>Trigger Error</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary fallback={<CustomErrorFallback />}>
      <MyComponent />
    </ErrorBoundary>
  );
}

function CustomErrorFallback() {
  return (
    <div className="custom-error">
      <h2>Oops! Something went wrong</h2>
      <p>We're sorry, but something unexpected happened.</p>
      <button onClick={() => window.location.reload()}>
        Refresh Page
      </button>
    </div>
  );
}
Advanced Error Boundary with Error Reporting
import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null, 
      errorInfo: null,
      errorId: null
    };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    const errorId = Date.now().toString();
    
    // Generate error report
    const errorReport = {
      id: errorId,
      error: {
        name: error.name,
        message: error.message,
        stack: error.stack
      },
      componentStack: errorInfo.componentStack,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href
    };
    
    // Log to console
    console.error('Error caught by boundary:', errorReport);
    
    // Send to error reporting service
    this.reportError(errorReport);
    
    this.setState({
      error: error,
      errorInfo: errorInfo,
      errorId: errorId
    });
  }

  reportError = async (errorReport) => {
    try {
      // Send to your error reporting service
      await fetch('/api/errors', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(errorReport)
      });
    } catch (reportingError) {
      console.error('Failed to report error:', reportingError);
    }
  }

  handleRetry = () => {
    this.setState({ 
      hasError: false, 
      error: null, 
      errorInfo: null,
      errorId: null 
    });
  }

  render() {
    if (this.state.hasError) {
      const isDevelopment = process.env.NODE_ENV === 'development';
      
      return (
        <div className="error-boundary">
          <div className="error-content">
            <h2>Something went wrong</h2>
            <p>We're sorry, but something unexpected happened.</p>
            
            {this.state.errorId && (
              <p className="error-id">Error ID: {this.state.errorId}</p>
            )}
            
            <div className="error-actions">
              <button onClick={this.handleRetry} className="retry-button">
                Try Again
              </button>
              <button onClick={() => window.location.reload()} className="reload-button">
                Reload Page
              </button>
            </div>
            
            {isDevelopment && (
              <details className="error-details">
                <summary>Error Details (Development Only)</summary>
                <div className="error-info">
                  <h4>Error:</h4>
                  <pre>{this.state.error && this.state.error.toString()}</pre>
                  
                  <h4>Component Stack:</h4>
                  <pre>{this.state.errorInfo.componentStack}</pre>
                </div>
              </details>
            )}
          </div>
        </div>
      );
    }

    return this.props.children;
  }
}

// Error boundary with different fallbacks for different error types
class SmartErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null, 
      errorType: null 
    };
  }

  static getDerivedStateFromError(error) {
    let errorType = 'unknown';
    
    if (error.name === 'ChunkLoadError') {
      errorType = 'chunk-load';
    } else if (error.message.includes('Network')) {
      errorType = 'network';
    } else if (error.message.includes('Permission')) {
      errorType = 'permission';
    }
    
    return { 
      hasError: true, 
      errorType 
    };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({ error });
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      const { fallbacks } = this.props;
      const fallback = fallbacks?.[this.state.errorType] || fallbacks?.default || <DefaultErrorFallback />;
      
      return React.cloneElement(fallback, {
        error: this.state.error,
        errorType: this.state.errorType,
        onRetry: () => this.setState({ hasError: false, error: null, errorType: null })
      });
    }

    return this.props.children;
  }
}

// Usage with custom fallbacks
function App() {
  const customFallbacks = {
    'chunk-load': <ChunkLoadErrorFallback />,
    'network': <NetworkErrorFallback />,
    'permission': <PermissionErrorFallback />,
    'default': <DefaultErrorFallback />
  };
  
  return (
    <SmartErrorBoundary fallbacks={customFallbacks}>
      <MyApp />
    </SmartErrorBoundary>
  );
}

function ChunkLoadErrorFallback({ onRetry }) {
  return (
    <div className="error-boundary">
      <h2>Failed to load application</h2>
      <p>Please check your internet connection and try again.</p>
      <button onClick={onRetry}>Retry</button>
    </div>
  );
}

function NetworkErrorFallback({ onRetry }) {
  return (
    <div className="error-boundary">
      <h2>Network Error</h2>
      <p>Unable to connect to the server. Please check your connection.</p>
      <button onClick={onRetry}>Retry</button>
    </div>
  );
}

function PermissionErrorFallback({ onRetry }) {
  return (
    <div className="error-boundary">
      <h2>Permission Denied</h2>
      <p>You don't have permission to access this resource.</p>
      <button onClick={() => window.location.href = '/login'}>
        Login
      </button>
    </div>
  );
}

function DefaultErrorFallback({ onRetry }) {
  return (
    <div className="error-boundary">
      <h2>Something went wrong</h2>
      <p>An unexpected error occurred. Please try again.</p>
      <button onClick={onRetry}>Retry</button>
    </div>
  );
}
Error Boundary Best Practices
// 1. Place error boundaries strategically
function App() {
  return (
    <div>
      {/* Top-level error boundary for critical errors */}
      <ErrorBoundary>
        <Header />
        
        {/* Error boundary for main content */}
        <ErrorBoundary>
          <MainContent />
        </ErrorBoundary>
        
        {/* Error boundary for less critical sections */}
        <ErrorBoundary>
          <Sidebar />
          <Footer />
        </ErrorBoundary>
      </ErrorBoundary>
    </div>
  );
}

// 2. Create reusable error boundary components
class FormErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Form error:', error, errorInfo);
    this.setState({ error });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="form-error">
          <p>There was an error with the form. Please try again.</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Retry
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function ContactForm() {
  return (
    <FormErrorBoundary>
      <form>
        {/* Form fields */}
      </form>
    </FormErrorBoundary>
  );
}

// 3. Error boundary for async operations
class AsyncErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Handle async errors differently
    if (error.name === 'ChunkLoadError') {
      // Handle code splitting errors
      console.error('Chunk load error:', error);
    } else {
      console.error('Async error:', error, errorInfo);
    }
    
    this.setState({ error });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="async-error">
          <h2>Loading Error</h2>
          <p>Failed to load content. Please refresh the page.</p>
        </div>
      );
    }

    return this.props.children;
  }
}

// 4. Error boundary with recovery mechanisms
class RecoverableErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null,
      retryCount: 0
    };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught:', error, errorInfo);
    this.setState({ error });
  }

  handleRetry = () => {
    const { retryCount } = this.state;
    const { maxRetries = 3 } = this.props;
    
    if (retryCount < maxRetries) {
      this.setState(prevState => ({
        hasError: false,
        error: null,
        retryCount: prevState.retryCount + 1
      }));
    } else {
      // Max retries reached, show different message
      console.error('Max retries reached');
    }
  }

  render() {
    if (this.state.hasError) {
      const { retryCount } = this.state;
      const { maxRetries = 3 } = this.props;
      
      return (
        <div className="recoverable-error">
          <h2>Something went wrong</h2>
          <p>
            {retryCount < maxRetries 
              ? `Attempt ${retryCount + 1} of ${maxRetries}`
              : 'Maximum retry attempts reached'
            }
          </p>
          
          {retryCount < maxRetries && (
            <button onClick={this.handleRetry}>
              Retry ({maxRetries - retryCount} attempts left)
            </button>
          )}
          
          {retryCount >= maxRetries && (
            <button onClick={() => window.location.reload()}>
              Reload Page
            </button>
          )}
        </div>
      );
    }

    return this.props.children;
  }
}

// 5. Error boundary for specific components
function withErrorBoundary(WrappedComponent, fallback) {
  return class WithErrorBoundary extends React.Component {
    render() {
      return (
        <ErrorBoundary fallback={fallback}>
          <WrappedComponent {...this.props} />
        </ErrorBoundary>
      );
    }
  };
}

// Usage
const SafeUserProfile = withErrorBoundary(UserProfile, <UserProfileErrorFallback />);

// 6. Error boundary HOC for multiple components
function withErrorBoundary(options = {}) {
  return function WrappedComponent {
    return class extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false, error: null };
      }

      static getDerivedStateFromError(error) {
        return { hasError: true };
      }

      componentDidCatch(error, errorInfo) {
        options.onError?.(error, errorInfo);
        this.setState({ error });
      }

      render() {
        if (this.state.hasError) {
          return options.fallback || <DefaultErrorFallback error={this.state.error} />;
        }

        return <WrappedComponent {...this.props} />;
      }
    };
  };
}

// Usage
const SafeComponent = withErrorBoundary({
  fallback: <CustomErrorFallback />,
  onError: (error, errorInfo) => {
    console.error('Component error:', error, errorInfo);
    // Send to error reporting service
  }
})(MyComponent);
Error Boundary Limitations
Does Not Catch Errors In
  • Event handlers
  • Asynchronous code (setTimeout, requestAnimationFrame)
  • Server-side rendering
  • Errors thrown in the error boundary itself
Workarounds
  • Use try-catch in event handlers
  • Use error boundaries with async/await
  • Wrap error-prone code in separate components
Testing Error Boundaries
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

// Component that throws an error
function BuggyComponent({ shouldThrow }) {
  if (shouldThrow) {
    throw new Error('Test error');
  }
  return <div>No error</div>;
}

// Test file
describe('ErrorBoundary', () => {
  // Suppress console.error for these tests
  const originalError = console.error;
  beforeEach(() => {
    console.error = jest.fn();
  });
  
  afterEach(() => {
    console.error = originalError;
  });

  test('renders children when there is no error', () => {
    render(
      <ErrorBoundary>
        <BuggyComponent shouldThrow={false} />
      </ErrorBoundary>
    );
    
    expect(screen.getByText('No error')).toBeInTheDocument();
    expect(screen.queryByText('Something went wrong')).not.toBeInTheDocument();
  });

  test('catches errors and renders fallback UI', () => {
    render(
      <ErrorBoundary>
        <BuggyComponent shouldThrow={true} />
      </ErrorBoundary>
    );
    
    expect(screen.queryByText('No error')).not.toBeInTheDocument();
    expect(screen.getByText('Something went wrong')).toBeInTheDocument();
  });

  test('calls componentDidCatch when error occurs', () => {
    const MockErrorBoundary = class extends ErrorBoundary {
      componentDidCatch(error, errorInfo) {
        super.componentDidCatch(error, errorInfo);
        this.props.onError?.(error, errorInfo);
      }
    };
    
    const onError = jest.fn();
    
    render(
      <MockErrorBoundary onError={onError}>
        <BuggyComponent shouldThrow={true} />
      </MockErrorBoundary>
    );
    
    expect(onError).toHaveBeenCalled();
    expect(onError.mock.calls[0][0]).toBeInstanceOf(Error);
    expect(onError.mock.calls[0][1]).toHaveProperty('componentStack');
  });

  test('can recover from error', () => {
    const { rerender } = render(
      <ErrorBoundary>
        <BuggyComponent shouldThrow={true} />
      </ErrorBoundary>
    );
    
    expect(screen.getByText('Something went wrong')).toBeInTheDocument();
    
    rerender(
      <ErrorBoundary>
        <BuggyComponent shouldThrow={false} />
      </ErrorBoundary>
    );
    
    expect(screen.getByText('No error')).toBeInTheDocument();
    expect(screen.queryByText('Something went wrong')).not.toBeInTheDocument();
  });
});
Module 17: Portals
Understanding React Portals

Portals provide a way to render children into a DOM node that exists outside the hierarchy of the parent component. This is useful for modals, tooltips, popovers, and other UI elements that need to break out of their container's visual constraints.

Basic Portal Usage
import React, { createPortal } from 'react';
import ReactDOM from 'react-dom';

function Modal({ children, isOpen, onClose }) {
  if (!isOpen) return null;
  
  return createPortal(
    <div className="modal-overlay">
      <div className="modal">
        <button className="modal-close" onClick={onClose}>
          ×
        </button>
        <div className="modal-content">
          {children}
        </div>
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

// Usage
function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  return (
    <div>
      <h1>My App</h1>
      <button onClick={() => setIsModalOpen(true)}>
        Open Modal
      </button>
      
      <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
        <h2>Modal Content</h2>
        <p>This is rendered outside the main app DOM!</p>
      </Modal>
    </div>
  );
}

// HTML structure needed
// <div id="root"></div>
// <div id="modal-root"></div>
Advanced Modal with Portal
import React, { createPortal, useEffect } from 'react';
import ReactDOM from 'react-dom';

function Modal({ 
  children, 
  isOpen, 
  onClose, 
  title, 
  size = 'medium',
  closeOnOverlayClick = true,
  closeOnEscape = true 
}) {
  useEffect(() => {
    const handleEscape = (e) => {
      if (e.key === 'Escape' && closeOnEscape) {
        onClose();
      }
    };
    
    if (isOpen) {
      document.addEventListener('keydown', handleEscape);
      document.body.style.overflow = 'hidden';
    }
    
    return () => {
      document.removeEventListener('keydown', handleEscape);
      document.body.style.overflow = 'unset';
    };
  }, [isOpen, onClose, closeOnEscape]);
  
  if (!isOpen) return null;
  
  const handleOverlayClick = (e) => {
    if (e.target === e.currentTarget && closeOnOverlayClick) {
      onClose();
    }
  };
  
  const modalSizeClass = `modal-${size}`;
  
  return createPortal(
    <div className="modal-overlay" onClick={handleOverlayClick}>
      <div 
        className={`modal ${modalSizeClass}`}
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
      >
        <div className="modal-header">
          <h2 id="modal-title">{title}</h2>
          <button 
            className="modal-close"
            onClick={onClose}
            aria-label="Close modal"
          >
            ×
          </button>
        </div>
        
        <div className="modal-body">
          {children}
        </div>
        
        <div className="modal-footer">
          <button className="btn btn-secondary" onClick={onClose}>
            Cancel
          </button>
          <button className="btn btn-primary" onClick={onClose}>
            Save
          </button>
        </div>
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

// Usage examples
function ConfirmModal({ isOpen, onClose, onConfirm, message }) {
  const handleConfirm = () => {
    onConfirm();
    onClose();
  };
  
  return (
    <Modal isOpen={isOpen} onClose={onClose} title="Confirm" size="small">
      <p>{message}</p>
      <div className="modal-footer">
        <button className="btn btn-secondary" onClick={onClose}>
          Cancel
        </button>
        <button className="btn btn-danger" onClick={handleConfirm}>
          Confirm
        </button>
      </div>
    </Modal>
  );
}

function ImageModal({ isOpen, onClose, image }) {
  return (
    <Modal isOpen={isOpen} onClose={onClose} title={image.title} size="large">
      <img 
        src={image.url} 
        alt={image.title} 
        style={{ maxWidth: '100%', height: 'auto' }}
      />
      <p>{image.description}</p>
    </Modal>
  );
}
Tooltip Portal
import React, { useState, useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';

function Tooltip({ text, children, placement = 'top' }) {
  const [isVisible, setIsVisible] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const containerRef = useRef(null);
  const tooltipRef = useRef(null);
  
  const showTooltip = () => {
    if (containerRef.current) {
      const rect = containerRef.current.getBoundingClientRect();
      const tooltipRect = tooltipRef.current?.getBoundingClientRect();
      
      let top, left;
      
      switch (placement) {
        case 'top':
          top = rect.top - (tooltipRect?.height || 0) - 10;
          left = rect.left + rect.width / 2 - (tooltipRect?.width || 0) / 2;
          break;
        case 'bottom':
          top = rect.bottom + 10;
          left = rect.left + rect.width / 2 - (tooltipRect?.width || 0) / 2;
          break;
        case 'left':
          top = rect.top + rect.height / 2 - (tooltipRect?.height || 0) / 2;
          left = rect.left - (tooltipRect?.width || 0) - 10;
          break;
        case 'right':
          top = rect.top + rect.height / 2 - (tooltipRect?.height || 0) / 2;
          left = rect.right + 10;
          break;
      }
      
      setPosition({ top, left });
      setIsVisible(true);
    }
  };
  
  const hideTooltip = () => {
    setIsVisible(false);
  };
  
  // Adjust position if tooltip goes out of viewport
  useEffect(() => {
    if (isVisible && tooltipRef.current) {
      const tooltipRect = tooltipRef.current.getBoundingClientRect();
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;
      
      let adjustedPosition = { ...position };
      
      // Adjust horizontal position
      if (position.left < 0) {
        adjustedPosition.left = 10;
      } else if (position.left + tooltipRect.width > viewportWidth) {
        adjustedPosition.left = viewportWidth - tooltipRect.width - 10;
      }
      
      // Adjust vertical position
      if (position.top < 0) {
        adjustedPosition.top = 10;
      } else if (position.top + tooltipRect.height > viewportHeight) {
        adjustedPosition.top = viewportHeight - tooltipRect.height - 10;
      }
      
      setPosition(adjustedPosition);
    }
  }, [isVisible, position]);
  
  return (
    <>
      <div
        ref={containerRef}
        onMouseEnter={showTooltip}
        onMouseLeave={hideTooltip}
        style={{ display: 'inline-block' }}
      >
        {children}
      </div>
      
      {isVisible && createPortal(
        <div
          ref={tooltipRef}
          className="tooltip"
          style={{
            position: 'absolute',
            top: `${position.top}px`,
            left: `${position.left}px`,
            zIndex: 1000,
            backgroundColor: '#333',
            color: 'white',
            padding: '8px 12px',
            borderRadius: '4px',
            fontSize: '14px',
            whiteSpace: 'nowrap',
            pointerEvents: 'none'
          }}
        >
          {text}
          <div 
            className="tooltip-arrow"
            style={{
              position: 'absolute',
              width: 0,
              height: 0,
              borderStyle: 'solid',
              borderWidth: placement === 'top' || placement === 'bottom' ? '5px 5px 0 5px' : '5px 0 5px 5px',
              borderColor: placement === 'top' ? '#333 transparent transparent transparent' : 
                           placement === 'bottom' ? 'transparent transparent #333 transparent' :
                           placement === 'left' ? 'transparent #333 transparent transparent' :
                           'transparent transparent transparent #333',
              bottom: placement === 'top' ? '-5px' : 'auto',
              top: placement === 'bottom' ? '-5px' : 'auto',
              right: placement === 'left' ? '-5px' : 'auto',
              left: placement === 'right' ? '-5px' : 'auto'
            }}
          />
        </div>,
        document.body
      )}
    </>
  );
}

// Usage
function App() {
  return (
    <div>
      <Tooltip text="This is a helpful tooltip">
        <button>Hover me</button>
      </Tooltip>
      
      <Tooltip text="Tooltip on the right side" placement="right">
        <input type="text" placeholder="Right tooltip" />
      </Tooltip>
      
      <Tooltip text="Tooltip on the bottom" placement="bottom">
        <span>Bottom tooltip</span>
      </Tooltip>
    </div>
  );
}
Notification System with Portal
import React, { useState, useEffect, createPortal } from 'react';
import ReactDOM from 'react-dom';

let notificationId = 0;

function NotificationContainer() {
  const [notifications, setNotifications] = useState([]);
  
  const addNotification = (message, type = 'info', duration = 5000) => {
    const id = notificationId++;
    const notification = {
      id,
      message,
      type,
      duration,
      timestamp: Date.now()
    };
    
    setNotifications(prev => [...prev, notification]);
    
    // Auto remove after duration
    if (duration > 0) {
      setTimeout(() => {
        removeNotification(id);
      }, duration);
    }
  };
  
  const removeNotification = (id) => {
    setNotifications(prev => prev.filter(n => n.id !== id));
  };
  
  // Expose addNotification globally
  useEffect(() => {
    window.showNotification = addNotification;
    window.hideNotification = removeNotification;
    
    return () => {
      delete window.showNotification;
      delete window.hideNotification;
    };
  }, []);
  
  return createPortal(
    <div className="notification-container">
      {notifications.map(notification => (
        <NotificationItem
          key={notification.id}
          notification={notification}
          onClose={() => removeNotification(notification.id)}
        />
      ))}
    </div>,
    document.getElementById('notification-root')
  );
}

function NotificationItem({ notification, onClose }) {
  const [isVisible, setIsVisible] = useState(false);
  
  useEffect(() => {
    // Trigger entrance animation
    setIsVisible(true);
    
    // Trigger exit animation before removal
    const timer = setTimeout(() => {
      setIsVisible(false);
    }, notification.duration - 300); // Leave 300ms for exit animation
    
    return () => clearTimeout(timer);
  }, [notification.duration]);
  
  const getIcon = () => {
    switch (notification.type) {
      case 'success':
        return '✓';
      case 'error':
        return '✗';
      case 'warning':
        return '⚠';
      default:
        return 'ℹ';
    }
  };
  
  return (
    <div 
      className={`notification notification-${notification.type} ${isVisible ? 'visible' : ''}`}
      onClick={onClose}
    >
      <div className="notification-icon">
        {getIcon()}
      </div>
      <div className="notification-content">
        {notification.message}
      </div>
      <button 
        className="notification-close"
        onClick={(e) => {
          e.stopPropagation();
          onClose();
        }}
      >
        ×
      </button>
    </div>
  );
}

// Usage
function App() {
  const showSuccess = () => {
    window.showNotification('Operation completed successfully!', 'success');
  };
  
  const showError = () => {
    window.showNotification('An error occurred. Please try again.', 'error');
  };
  
  const showWarning = () => {
    window.showNotification('Please save your changes before leaving.', 'warning');
  };
  
  const showPersistent = () => {
    window.showNotification('This notification will stay until you close it.', 'info', 0);
  };
  
  return (
    <div>
      <NotificationContainer />
      
      <h1>Notification Demo</h1>
      
      <div className="button-group">
        <button onClick={showSuccess}>Show Success</button>
        <button onClick={showError}>Show Error</button>
        <button onClick={showWarning}>Show Warning</button>
        <button onClick={showPersistent}>Show Persistent</button>
      </div>
    </div>
  );
}
Dropdown Menu with Portal
import React, { useState, useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';

function Dropdown({ trigger, children, placement = 'bottom-left' }) {
  const [isOpen, setIsOpen] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const triggerRef = useRef(null);
  const dropdownRef = useRef(null);
  
  const toggleDropdown = () => {
    setIsOpen(!isOpen);
  };
  
  const closeDropdown = () => {
    setIsOpen(false);
  };
  
  // Calculate position when dropdown opens
  useEffect(() => {
    if (isOpen && triggerRef.current && dropdownRef.current) {
      const triggerRect = triggerRef.current.getBoundingClientRect();
      const dropdownRect = dropdownRef.current.getBoundingClientRect();
      
      let top, left;
      
      switch (placement) {
        case 'bottom-left':
          top = triggerRect.bottom + 5;
          left = triggerRect.left;
          break;
        case 'bottom-right':
          top = triggerRect.bottom + 5;
          left = triggerRect.right - dropdownRect.width;
          break;
        case 'top-left':
          top = triggerRect.top - dropdownRect.height - 5;
          left = triggerRect.left;
          break;
        case 'top-right':
          top = triggerRect.top - dropdownRect.height - 5;
          left = triggerRect.right - dropdownRect.width;
          break;
      }
      
      setPosition({ top, left });
    }
  }, [isOpen, placement]);
  
  // Close dropdown when clicking outside
  useEffect(() => {
    const handleClickOutside = (event) => {
      if (
        isOpen && 
        dropdownRef.current && 
        !dropdownRef.current.contains(event.target) &&
        triggerRef.current && 
        !triggerRef.current.contains(event.target)
      ) {
        closeDropdown();
      }
    };
    
    if (isOpen) {
      document.addEventListener('mousedown', handleClickOutside);
    }
    
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [isOpen]);
  
  // Close dropdown on escape key
  useEffect(() => {
    const handleEscape = (event) => {
      if (event.key === 'Escape' && isOpen) {
        closeDropdown();
      }
    };
    
    if (isOpen) {
      document.addEventListener('keydown', handleEscape);
    }
    
    return () => {
      document.removeEventListener('keydown', handleEscape);
    };
  }, [isOpen]);
  
  return (
    <>
      <div 
        ref={triggerRef}
        onClick={toggleDropdown}
        className="dropdown-trigger"
      >
        {trigger}
      </div>
      
      {isOpen && createPortal(
        <div
          ref={dropdownRef}
          className="dropdown-menu"
          style={{
            position: 'absolute',
            top: `${position.top}px`,
            left: `${position.left}px`,
            zIndex: 1000,
            backgroundColor: 'white',
            border: '1px solid #ccc',
            borderRadius: '4px',
            boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
            minWidth: '200px'
          }}
        >
          {children}
        </div>,
        document.body
      )}
    </>
  );
}

// Usage
function UserMenu() {
  const handleProfile = () => {
    console.log('Navigate to profile');
  };
  
  const handleSettings = () => {
    console.log('Navigate to settings');
  };
  
  const handleLogout = () => {
    console.log('Logout user');
  };
  
  return (
    <Dropdown 
      trigger={
        <button className="user-menu-button">
          <img src="/avatar.jpg" alt="User" className="avatar" />
          <span>John Doe</span>
          <span className="dropdown-arrow">▼</span>
        </button>
      }
      placement="bottom-right"
    >
      <div className="dropdown-item" onClick={handleProfile}>
        <i className="icon-user"></i>
        Profile
      </div>
      <div className="dropdown-item" onClick={handleSettings}>
        <i className="icon-settings"></i>
        Settings
      </div>
      <hr className="dropdown-divider" />
      <div className="dropdown-item" onClick={handleLogout}>
        <i className="icon-logout"></i>
        Logout
      </div>
    </Dropdown>
  );
}

// Context menu with portal
function useContextMenu() {
  const [contextMenu, setContextMenu] = useState(null);
  
  const showContextMenu = (event, menuItems) => {
    event.preventDefault();
    
    setContextMenu({
      x: event.clientX,
      y: event.clientY,
      items: menuItems
    });
  };
  
  const hideContextMenu = () => {
    setContextMenu(null);
  };
  
  useEffect(() => {
    const handleClick = () => {
      hideContextMenu();
    };
    
    if (contextMenu) {
      document.addEventListener('click', handleClick);
    }
    
    return () => {
      document.removeEventListener('click', handleClick);
    };
  }, [contextMenu]);
  
  const ContextMenuComponent = () => {
    if (!contextMenu) return null;
    
    return createPortal(
      <div
        className="context-menu"
        style={{
          position: 'fixed',
          top: `${contextMenu.y}px`,
          left: `${contextMenu.x}px`,
          zIndex: 1000
        }}
      >
        {contextMenu.items.map((item, index) => (
          <div
            key={index}
            className="context-menu-item"
            onClick={() => {
              item.onClick();
              hideContextMenu();
            }}
          >
            {item.icon && <span className="context-menu-icon">{item.icon}</span>}
            {item.label}
          </div>
        ))}
      </div>,
      document.body
    );
  };
  
  return { showContextMenu, hideContextMenu, ContextMenuComponent };
}

// Usage
function App() {
  const { showContextMenu, hideContextMenu, ContextMenuComponent } = useContextMenu();
  
  const handleContextMenu = (e) => {
    showContextMenu(e, [
      {
        label: 'Cut',
        icon: '✂',
        onClick: () => console.log('Cut')
      },
      {
        label: 'Copy',
        icon: '📋',
        onClick: () => console.log('Copy')
      },
      {
        label: 'Paste',
        icon: '📄',
        onClick: () => console.log('Paste')
      },
      {
        label: 'Delete',
        icon: '🗑',
        onClick: () => console.log('Delete')
      }
    ]);
  };
  
  return (
    <div>
      <ContextMenuComponent />
      
      <div 
        className="context-area"
        onContextMenu={handleContextMenu}
        style={{
          width: '300px',
          height: '200px',
          border: '1px solid #ccc',
          padding: '20px',
          backgroundColor: '#f5f5f5'
        }}
      >
        Right-click here for context menu
      </div>
      
      <UserMenu />
    </div>
  );
}
Portal Best Practices
Clean Up Effects

Always clean up event listeners and timeouts when using portals.

Handle Escape Key

Implement keyboard navigation and escape key handling for accessibility.

Position Carefully

Calculate positions carefully to avoid elements going off-screen.

Focus Management

Properly manage focus when opening/closing portal elements.

Z-index Management

Use appropriate z-index values to ensure proper layering.

Module 18: Advanced Context API
Advanced Context Patterns

Context API is a powerful feature in React for sharing data between components without prop drilling. Advanced patterns can help optimize performance, organize code better, and create more maintainable applications.

Optimized Context with Selectors
import React, { createContext, useContext, useMemo, useCallback } from 'react';

// Create context with optimized selectors
const AppContext = createContext();

function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);
  
  // Memoize context value to prevent unnecessary re-renders
  const contextValue = useMemo(() => ({
    state,
    dispatch
  }), [state]);
  
  return (
    <AppContext.Provider value={contextValue}>
      {children}
    </AppContext.Provider>
  );
}

// Custom hook with selector for optimized subscriptions
function useContextSelector(selector) {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useContextSelector must be used within AppProvider');
  }
  
  // Memoize the selected value to prevent unnecessary re-renders
  return useMemo(() => selector(context.state), [context.state, selector]);
}

// Custom hook with action creators
function useContextActions() {
  const { dispatch } = useContext(AppContext);
  if (!dispatch) {
    throw new Error('useContextActions must be used within AppProvider');
  }
  
  // Memoize action creators to prevent unnecessary re-renders
  return useMemo(() => ({
    setUser: useCallback((user) => dispatch({ type: 'SET_USER', payload: user }), [dispatch]),
    setTheme: useCallback((theme) => dispatch({ type: 'SET_THEME', payload: theme }), [dispatch]),
    addNotification: useCallback((notification) => dispatch({ type: 'ADD_NOTIFICATION', payload: notification }), [dispatch]),
    removeNotification: useCallback((id) => dispatch({ type: 'REMOVE_NOTIFICATION', payload: id }), [dispatch])
  }), [dispatch]);
}

// Usage examples
function UserName() {
  const userName = useContextSelector(state => state.user?.name);
  return <span>{userName || 'Guest'}</span>;
}

function UserAvatar() {
  const userAvatar = useContextSelector(state => state.user?.avatar);
  return <img src={userAvatar || '/default-avatar.png'} alt="User" />;
}

function ThemeToggle() {
  const theme = useContextSelector(state => state.theme);
  const { setTheme } = useContextActions();
  
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };
  
  return (
    <button onClick={toggleTheme}>
      Current theme: {theme}
    </button>
  );
}

// This component only re-renders when notifications change
function NotificationList() {
  const notifications = useContextSelector(state => state.notifications);
  const { removeNotification } = useContextActions();
  
  return (
    <div>
      {notifications.map(notification => (
        <div key={notification.id}>
          {notification.message}
          <button onClick={() => removeNotification(notification.id)}>
            Dismiss
          </button>
        </div>
      ))}
    </div>
  );
}
Multiple Contexts with Composition
// Separate contexts for different concerns
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();

// User context provider
function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Load user from localStorage or API
    const loadUser = async () => {
      try {
        const token = localStorage.getItem('token');
        if (token) {
          const userData = await validateToken(token);
          setUser(userData);
        }
      } catch (error) {
        console.error('Failed to load user:', error);
      } finally {
        setLoading(false);
      }
    };
    
    loadUser();
  }, []);
  
  const login = async (credentials) => {
    setLoading(true);
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });
      
      if (!response.ok) throw new Error('Login failed');
      
      const { token, user } = await response.json();
      localStorage.setItem('token', token);
      setUser(user);
    } catch (error) {
      console.error('Login error:', error);
      throw error;
    } finally {
      setLoading(false);
    }
  };
  
  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };
  
  const value = useMemo(() => ({
    user,
    loading,
    login,
    logout,
    isAuthenticated: !!user
  }), [user, loading, login, logout]);
  
  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

// Theme context provider
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const value = useMemo(() => ({
    theme,
    setTheme,
    toggleTheme,
    colors: theme === 'light' ? {
      background: '#ffffff',
      text: '#000000',
      primary: '#007bff'
    } : {
      background: '#1a1a1a',
      text: '#ffffff',
      primary: '#0d6efd'
    }
  }), [theme, setTheme, toggleTheme]);
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

// Notification context provider
function NotificationProvider({ children }) {
  const [notifications, setNotifications] = useState([]);
  
  const addNotification = useCallback((notification) => {
    const id = Date.now();
    const newNotification = { ...notification, id };
    setNotifications(prev => [...prev, newNotification]);
    
    // Auto-remove after duration
    if (notification.duration) {
      setTimeout(() => {
        setNotifications(prev => prev.filter(n => n.id !== id));
      }, notification.duration);
    }
  }, []);
  
  const removeNotification = useCallback((id) => {
    setNotifications(prev => prev.filter(n => n.id !== id));
  }, []);
  
  const clearNotifications = useCallback(() => {
    setNotifications([]);
  }, []);
  
  const value = useMemo(() => ({
    notifications,
    addNotification,
    removeNotification,
    clearNotifications
  }), [notifications, addNotification, removeNotification, clearNotifications]);
  
  return (
    <NotificationContext.Provider value={value}>
      {children}
    </NotificationContext.Provider>
  );
}

// Custom hooks for each context
function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

function useNotifications() {
  const context = useContext(NotificationContext);
  if (!context) {
    throw new Error('useNotifications must be used within NotificationProvider');
  }
  return context;
}

// Combined provider
function AppProviders({ children }) {
  return (
    <UserProvider>
      <ThemeProvider>
        <NotificationProvider>
          {children}
        </NotificationProvider>
      </ThemeProvider>
    </UserProvider>
  );
}

// Usage
function App() {
  return (
    <AppProviders>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/profile" element={<ProtectedRoute><Profile /></ProtectedRoute>} />
        </Routes>
      </Router>
    </AppProviders>
  );
}

function Profile() {
  const { user } = useUser();
  const { theme, colors } = useTheme();
  const { addNotification } = useNotifications();
  
  const handleSave = async () => {
    try {
      await saveProfile(user);
      addNotification({
        message: 'Profile saved successfully!',
        type: 'success',
        duration: 3000
      });
    } catch (error) {
      addNotification({
        message: 'Failed to save profile',
        type: 'error',
        duration: 5000
      });
    }
  };
  
  return (
    <div style={{ backgroundColor: colors.background, color: colors.text }}>
      <h1>Profile</h1>
      <p>Welcome, {user?.name}!</p>
      <p>Current theme: {theme}</p>
      <button onClick={handleSave}>Save Profile</button>
    </div>
  );
}
Context with Middleware Pattern
// Context middleware pattern for logging, persistence, etc.
function createContextWithMiddleware(reducer, initialState, middleware = []) {
  const Context = createContext();
  
  function Provider({ children }) {
    const [state, dispatch] = useReducer(reducer, initialState);
    
    // Apply middleware to dispatch
    const enhancedDispatch = useMemo(() => {
      return middleware.reduceRight((acc, middleware) => {
        return middleware(acc, dispatch);
      }, dispatch);
    }, [dispatch]);
    
    const value = useMemo(() => ({
      state,
      dispatch: enhancedDispatch
    }), [state, enhancedDispatch]);
    
    return (
      <Context.Provider value={value}>
        {children}
      </Context.Provider>
    );
  }
  
  return { Context, Provider };
}

// Logging middleware
const loggingMiddleware = (dispatch, originalDispatch) => (action) => {
  console.log('Dispatching action:', action);
  const result = dispatch(action);
  console.log('State after action:', result);
  return result;
};

// Persistence middleware
const persistenceMiddleware = (dispatch, originalDispatch) => (action) => {
  const result = dispatch(action);
  
  // Save state to localStorage
  if (action.type !== 'LOAD_FROM_STORAGE') {
    try {
      localStorage.setItem('appState', JSON.stringify(result));
    } catch (error) {
      console.error('Failed to save state:', error);
    }
  }
  
  return result;
};

// Analytics middleware
const analyticsMiddleware = (dispatch, originalDispatch) => (action) => {
  // Track user actions
  if (typeof gtag !== 'undefined') {
    gtag('event', 'user_action', {
      action_type: action.type,
      action_payload: JSON.stringify(action.payload)
    });
  }
  
  return dispatch(action);
};

// Create context with middleware
const { Context: AppContext, Provider: AppProvider } = createContextWithMiddleware(
  appReducer,
  initialState,
  [loggingMiddleware, persistenceMiddleware, analyticsMiddleware]
);

// Usage
function App() {
  return (
    <AppProvider>
      <MyApp />
    </AppProvider>
  );
}
Context with State Machines
// State machine pattern for complex state logic
function createStateMachine(config) {
  const { initialState, states } = config;
  
  return (state = initialState, event) => {
    const currentState = states[state];
    if (!currentState) {
      throw new Error(`Invalid state: ${state}`);
    }
    
    const transition = currentState.on?.[event.type];
    if (!transition) {
      return state;
    }
    
    if (transition.guard && !transition.guard(event.payload)) {
      return state;
    }
    
    const nextState = transition.target || state;
    
    // Execute actions
    if (transition.action) {
      transition.action(event.payload);
    }
    
    return nextState;
  };
}

// Form state machine
const formStateMachine = createStateMachine({
  initialState: 'idle',
  states: {
    idle: {
      on: {
        SUBMIT: {
          target: 'submitting',
          guard: (payload) => payload.isValid,
          action: (payload) => console.log('Submitting form:', payload)
        }
      }
    },
    submitting: {
      on: {
        SUCCESS: {
          target: 'success',
          action: (payload) => console.log('Form submitted successfully:', payload)
        },
        ERROR: {
          target: 'error',
          action: (payload) => console.log('Form submission failed:', payload)
        }
      }
    },
    success: {
      on: {
        RESET: {
          target: 'idle',
          action: () => console.log('Form reset')
        }
      }
    },
    error: {
      on: {
        RETRY: {
          target: 'submitting'
        },
        RESET: {
          target: 'idle'
        }
      }
    }
  }
});

function useFormMachine(initialData, validationSchema) {
  const [state, dispatch] = useReducer(formStateMachine, 'idle');
  const [formData, setFormData] = useState(initialData);
  const [errors, setErrors] = useState({});
  
  const submit = async () => {
    // Validate form
    const validationErrors = validateForm(formData, validationSchema);
    setErrors(validationErrors);
    
    if (Object.keys(validationErrors).length > 0) {
      return;
    }
    
    dispatch({ type: 'SUBMIT', payload: { isValid: true, data: formData } });
    
    try {
      const response = await fetch('/api/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData)
      });
      
      if (!response.ok) throw new Error('Submission failed');
      
      const result = await response.json();
      dispatch({ type: 'SUCCESS', payload: result });
    } catch (error) {
      dispatch({ type: 'ERROR', payload: { error: error.message } });
    }
  };
  
  const reset = () => {
    setFormData(initialData);
    setErrors({});
    dispatch({ type: 'RESET' });
  };
  
  const retry = () => {
    dispatch({ type: 'RETRY' });
  };
  
  return {
    state,
    formData,
    errors,
    setFormData,
    submit,
    reset,
    retry
  };
}

// Usage
function ContactForm() {
  const formMachine = useFormMachine(
    { name: '', email: '', message: '' },
    {
      name: { required: true },
      email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
      message: { required: true, minLength: 10 }
    }
  );
  
  const { state, formData, errors, setFormData, submit, reset, retry } = formMachine;
  
  return (
    <form onSubmit={(e) => { e.preventDefault(); submit(); }}>
      <div>
        <label>Name:</label>
        <input
          value={formData.name}
          onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>
      
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <label>Message:</label>
        <textarea
          value={formData.message}
          onChange={(e) => setFormData({ ...formData, message: e.target.value })}
        />
        {errors.message && <span className="error">{errors.message}</span>}
      </div>
      
      <div>
        <button type="submit" disabled={state === 'submitting'}>
          {state === 'submitting' ? 'Submitting...' : 'Submit'}
        </button>
        
        {state === 'error' && (
          <>
            <button type="button" onClick={retry}>
              Retry
            </button>
            <button type="button" onClick={reset}>
              Reset
            </button>
          </>
        )}
        
        {state === 'success' && (
          <button type="button" onClick={reset}>
            Submit Another
          </button>
        )}
      </div>
      
      {state === 'success' && (
        <div className="success-message">
          Form submitted successfully!
        </div>
      )}
      
      {state === 'error' && (
        <div className="error-message">
          Submission failed. Please try again.
        </div>
      )}
    </form>
  );
}
Context with Performance Optimization
// Optimized context with subscription management
function createOptimizedContext(defaultValue) {
  const Context = createContext(defaultValue);
  
  function Provider({ value, children }) {
    // Use a ref to store the current value to avoid re-renders
    const valueRef = useRef(value);
    
    // Update ref when value changes
    useEffect(() => {
      valueRef.current = value;
    }, [value]);
    
    // Create a subscription map
    const subscriptions = useRef(new Map());
    
    // Subscribe to context changes
    const subscribe = (callback, selector) => {
      const id = Symbol();
      const subscription = { callback, selector };
      
      subscriptions.current.set(id, subscription);
      
      // Call callback immediately with current value
      const currentValue = selector ? selector(valueRef.current) : valueRef.current;
      callback(currentValue);
      
      // Return unsubscribe function
      return () => {
        subscriptions.current.delete(id);
      };
    };
    
    // Notify subscribers when value changes
    useEffect(() => {
      subscriptions.current.forEach(({ callback, selector }) => {
        const currentValue = selector ? selector(valueRef.current) : valueRef.current;
        callback(currentValue);
      });
    }, [value]);
    
    const contextValue = useMemo(() => ({
      value: valueRef.current,
      subscribe
    }), []);
    
    return (
      <Context.Provider value={contextValue}>
        {children}
      </Context.Provider>
    );
  }
  
  // Custom hook for optimized subscriptions
  function useContextSubscription(selector, equalityFn = Object.is) {
    const context = useContext(Context);
    if (!context) {
      throw new Error('useContextSubscription must be used within Provider');
    }
    
    const [selectedValue, setSelectedValue] = useState(() => 
      selector ? selector(context.value) : context.value
    );
    
    const selectorRef = useRef(selector);
    const equalityFnRef = useRef(equalityFn);
    const selectedValueRef = useRef(selectedValue);
    
    useEffect(() => {
      return context.subscribe((newValue) => {
        const newSelectedValue = selectorRef.current ? selectorRef.current(newValue) : newValue;
        
        if (!equalityFnRef.current(selectedValueRef.current, newSelectedValue)) {
          selectedValueRef.current = newSelectedValue;
          setSelectedValue(newSelectedValue);
        }
      }, selectorRef.current);
    }, [context.subscribe]);
    
    return selectedValue;
  }
  
  return { Context, Provider, useContextSubscription };
}

// Usage
const { Context: UserContext, Provider: UserProvider, useContextSubscription: useUserSubscription } = createOptimizedContext({
  user: null,
  loading: false,
  error: null
});

function App() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const value = useMemo(() => ({
    user,
    loading,
    error: null,
    setUser,
    setLoading
  }), [user, loading]);
  
  return (
    <UserProvider value={value}>
      <Dashboard />
      <Sidebar />
    </UserProvider>
  );
}

// Component that only re-renders when user.name changes
function UserName() {
  const userName = useUserSubscription(
    state => state.user?.name,
    (prev, next) => prev === next
  );
  
  return <span>{userName || 'Guest'}</span>;
}

// Component that only re-renders when loading state changes
function LoadingIndicator() {
  const isLoading = useUserSubscription(
    state => state.loading,
    (prev, next) => prev === next
  );
  
  return isLoading ? <div>Loading...</div> : null;
}

// Component that subscribes to multiple values
function UserInfo() {
  const userInfo = useUserSubscription(
    state => ({
      name: state.user?.name,
      email: state.user?.email,
      loading: state.loading
    }),
    (prev, next) => 
      prev.name === next.name && 
      prev.email === next.email && 
      prev.loading === next.loading
  );
  
  if (userInfo.loading) {
    return <div>Loading user info...</div>;
  }
  
  return (
    <div>
      <h2>{userInfo.name}</h2>
      <p>{userInfo.email}</p>
    </div>
  );
}
Context Best Practices
Split Contexts

Split contexts by domain rather than having one large context.

Use Selectors

Use selectors to prevent unnecessary re-renders.

Avoid Context for Everything

Don't use context for props that don't change often or are only needed by few components.

Optimize Provider Value

Memoize the context value to prevent unnecessary re-renders.

Consider State Management Libraries

For complex state management, consider libraries like Redux, Zustand, or Jotai.