1. React Components and JSX Syntax

1.1 Functional Components and Arrow Function Syntax

Pattern Syntax Description Use Case
Function Declaration function Comp() {} Traditional function syntax for components Named exports, hoisting needed
Arrow Function const Comp = () => {} Modern ES6 arrow function syntax Preferred for most components
Implicit Return const Comp = () => (<div/>) Single expression return without braces Simple, presentational components
Named Export export function Comp() {} Export component directly Multiple exports per file
Default Export export default Comp Single default export per file One main component per file

Example: Component declaration patterns

// Function declaration
function Welcome() {
  return <h1>Hello</h1>;
}

// Arrow function with explicit return
const Greeting = () => {
  return <h1>Hi there</h1>;
};

// Arrow function with implicit return
const SimpleComp = () => (<div>Simple</div>);

// With props destructuring
const User = ({ name, age }) => (
  <div>{name}, {age}</div>
);

1.2 JSX Elements and Expression Embedding

Feature Syntax Description Example
JSX Element <div>content</div> HTML-like syntax in JavaScript <h1>Title</h1>
Self-closing Tag <Component /> Elements without children must self-close <img src="..." />
Expression Embedding {expression} Execute JavaScript inside JSX {2 + 2}
Attribute Expression attr={value} Dynamic attribute values className={style}
String Literal attr="value" Static string attributes type="text"
CamelCase Props className, onClick HTML attributes use camelCase in JSX tabIndex, htmlFor
Comments in JSX {/* comment */} JavaScript-style comments in curly braces {/* Note */}

Example: JSX expressions and embedding

const UserCard = ({ user, isOnline }) => {
  const greeting = 'Welcome';
  
  return (
    <div className="card">
      {/* Dynamic content */}
      <h2>{greeting}, {user.name}!</h2>
      
      {/* Expressions */}
      <p>Age: {user.age * 2}</p>
      
      {/* Method calls */}
      <p>{user.email.toLowerCase()}</p>
      
      {/* Dynamic attributes */}
      <img src={user.avatar} alt={user.name} />
      <span className={isOnline ? 'active' : 'inactive'}>
        {isOnline ? 'Online' : 'Offline'}
      </span>
    </div>
  );
};
Note: JSX is syntactic sugar for React.createElement(). All JSX is transformed to JavaScript function calls during compilation.

1.3 Component Props and Destructuring Patterns

Pattern Syntax Description Use Case
Props Object function Comp(props) {} Access props via object When passing all props down
Destructuring ({ name, age }) => {} Extract props directly in parameters Most common pattern
Default Values ({ name = 'Guest' }) => {} Provide fallback values Optional props with defaults
Rest Props ({ name, ...rest }) => {} Collect remaining props Forwarding props to children
Nested Destructuring ({ user: { name } }) => {} Extract nested object properties Complex prop structures
Prop Spreading <Comp {...props} /> Pass all props to child Wrapper components

Example: Props destructuring patterns

// Basic destructuring with defaults
const Button = ({ 
  text = 'Click', 
  onClick, 
  disabled = false 
}) => (
  <button onClick={onClick} disabled={disabled}>
    {text}
  </button>
);

// Rest props and spreading
const Input = ({ label, ...inputProps }) => (
  <div>
    <label>{label}</label>
    <input {...inputProps} />
  </div>
);

// Nested destructuring
const UserProfile = ({ 
  user: { name, email, address: { city } } 
}) => (
  <div>
    <h2>{name}</h2>
    <p>{email}</p>
    <p>City: {city}</p>
  </div>
);

// Usage with explicit props
<Input label="Email" type="email" placeholder="Enter email" />

1.4 Children Prop and Component Composition

Concept Syntax Description Use Case
Children Prop props.children Content passed between component tags Wrapper/container components
Children Destructuring ({ children }) => {} Extract children directly Cleaner component code
Multiple Children <Comp>{child1}{child2}</Comp> Pass multiple elements as children Complex layouts
Render Props children(data) Children as function pattern Share logic/data with children
Named Slots header, footer props Multiple named content areas Flexible layouts
Composition <A><B><C /></B></A> Nest components for hierarchy Building complex UIs

Example: Children and composition patterns

// Simple children wrapper
const Card = ({ children, title }) => (
  <div className="card">
    <h3>{title}</h3>
    <div className="card-body">{children}</div>
  </div>
);

// Render props pattern
const DataProvider = ({ children, data }) => (
  <div>{children(data)}</div>
);

// Usage
<DataProvider data={users}>
  {(users) => (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  )}
</DataProvider>

// Named slots pattern
const Layout = ({ header, children, footer }) => (
  <div>
    <header>{header}</header>
    <main>{children}</main>
    <footer>{footer}</footer>
  </div>
);

// Composition
<Layout 
  header={<Nav />}
  footer={<Footer />}
>
  <Card title="Welcome">
    <p>Content here</p>
  </Card>
</Layout>

1.5 JSX Conditional Rendering Patterns

Pattern Syntax Description Use Case
Ternary Operator {condition ? <A/> : <B/>} Inline if-else rendering Two-way conditional rendering
Logical AND (&&) {condition && <Component/>} Render if condition is truthy Optional content display
Logical OR (||) {value || 'default'} Fallback value if falsy Default content
Nullish Coalescing {value ?? 'default'} Fallback only for null/undefined Preserve falsy values (0, '')
Early Return if (!data) return null Guard clause before JSX Loading/error states
IIFE Pattern {(() => {})()} Complex logic in JSX Multi-statement conditionals
Element Variables let el; if (...) el = ... Store JSX in variables Complex conditional logic

Example: Conditional rendering techniques

const UserStatus = ({ user, isLoading, error }) => {
  // Early return for loading
  if (isLoading) return <Spinner />;
  
  // Early return for error
  if (error) return <Error message={error} />;
  
  // Early return for no data
  if (!user) return null;
  
  return (
    <div>
      {/* Ternary operator */}
      {user.isPremium ? (
        <PremiumBadge />
      ) : (
        <FreeBadge />
      )}
      
      {/* Logical AND */}
      {user.notifications && (
        <NotificationBadge count={user.notifications} />
      )}
      
      {/* Nullish coalescing */}
      <p>Score: {user.score ?? 0}</p>
      
      {/* Multiple conditions with element variable */}
      {(() => {
        if (user.role === 'admin') return <AdminPanel />;
        if (user.role === 'moderator') return <ModPanel />;
        return <UserPanel />;
      })()}
    </div>
  );
};

// Element variable pattern
const StatusDisplay = ({ status }) => {
  let statusElement;
  
  switch(status) {
    case 'success':
      statusElement = <SuccessIcon />;
      break;
    case 'error':
      statusElement = <ErrorIcon />;
      break;
    default:
      statusElement = <InfoIcon />;
  }
  
  return <div>{statusElement}</div>;
};
Warning: Avoid using && with numbers like {count && <Text/>} - if count is 0, it renders "0". Use {count > 0 && <Text/>} or {!!count && <Text/>} instead.

1.6 JSX Fragment Syntax and React.Fragment

Syntax Usage Description Use Case
Short Syntax <>...</> Empty tag fragment shorthand Group elements without wrapper
Explicit Fragment <React.Fragment> Full fragment component When key prop is needed
Fragment with Key <Fragment key={id}> Fragment in lists with key Mapping multiple elements
Import Fragment import { Fragment } Named import from React Cleaner than React.Fragment

Example: Fragment usage patterns

import { Fragment } from 'react';

// Short syntax - most common
const UserInfo = () => (
  <>
    <h1>User Profile</h1>
    <p>Details</p>
  </>
);

// With key prop (required in lists)
const GlossaryList = ({ items }) => (
  <dl>
    {items.map(item => (
      <Fragment key={item.id}>
        <dt>{item.term}</dt>
        <dd>{item.description}</dd>
      </Fragment>
    ))}
  </dl>
);

// Avoiding unnecessary divs
const Table = () => (
  <table>
    <tbody>
      {data.map(row => (
        <>
          <tr key={row.id}>
            <td>{row.name}</td>
          </tr>
          {row.details && (
            <tr>
              <td colSpan="2">{row.details}</td>
            </tr>
          )}
        </>
      ))}
    </tbody>
  </table>
);

// Conditional multiple elements
const Form = ({ showHelp }) => (
  <form>
    <input type="text" />
    {showHelp && (
      <>
        <p>Help text</p>
        <button>Learn More</button>
      </>
    )}
  </form>
);
Note: Fragments don't create extra DOM nodes, improving performance and maintaining semantic HTML. Use <></> when no props needed, <Fragment key={...}> when mapping with keys.

2. React Hooks Fundamentals

2.1 useState Hook and State Management

Pattern Syntax Description Use Case
Basic useState const [state, setState] = useState(initial) Declare state variable with setter Simple state values
Lazy Initialization useState(() => expensive()) Initialize state with function Expensive initial computation
Functional Update setState(prev => prev + 1) Update based on previous state Batched updates, async operations
Multiple States useState() for each value Split unrelated state into separate hooks Independent state management
Object State useState({ key: value }) Store related data together Form data, related properties
Array State useState([]) Manage lists and collections Todo lists, dynamic items

Example: useState patterns and updates

import { useState } from 'react';

// Basic counter
const Counter = () => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      {/* Functional update - safer for async */}
      <button onClick={() => setCount(prev => prev + 1)}>+</button>
    </div>
  );
};

// Lazy initialization
const ExpensiveComponent = () => {
  const [data, setData] = useState(() => {
    return computeExpensiveValue();
  });
  
  return <div>{data}</div>;
};

// Object state with spreading
const Form = () => {
  const [form, setForm] = useState({ name: '', email: '' });
  
  const updateField = (field, value) => {
    setForm(prev => ({ ...prev, [field]: value }));
  };
  
  return (
    <form>
      <input 
        value={form.name}
        onChange={(e) => updateField('name', e.target.value)}
      />
      <input 
        value={form.email}
        onChange={(e) => updateField('email', e.target.value)}
      />
    </form>
  );
};

// Array state operations
const TodoList = () => {
  const [todos, setTodos] = useState([]);
  
  const addTodo = (text) => {
    setTodos(prev => [...prev, { id: Date.now(), text }]);
  };
  
  const removeTodo = (id) => {
    setTodos(prev => prev.filter(t => t.id !== id));
  };
  
  const updateTodo = (id, newText) => {
    setTodos(prev => prev.map(t => 
      t.id === id ? { ...t, text: newText } : t
    ));
  };
  
  return <ul>{/* render todos */}</ul>;
};
Warning: State updates are asynchronous and batched. Always use functional updates setState(prev => ...) when new state depends on previous state.

2.2 useEffect Hook and Side Effects

Pattern Syntax Description Use Case
Basic Effect useEffect(() => {}) Runs after every render DOM updates, logging (avoid)
Effect with Dependencies useEffect(() => {}, [deps]) Runs when dependencies change Data fetching, subscriptions
Mount Only Effect useEffect(() => {}, []) Runs once on mount Initial setup, API calls
Cleanup Function useEffect(() => { return () => {} }) Cleanup before unmount/re-run Unsubscribe, clear timers
Multiple Effects useEffect() for each concern Separate unrelated side effects Better code organization
Conditional Effect if (condition) { /* effect */ } Execute effect conditionally inside Conditional subscriptions

Example: useEffect patterns and cleanup

import { useState, useEffect } from 'react';

// Data fetching with cleanup
const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    let isMounted = true; // Cleanup flag
    
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(\`/api/users/\${userId}\`);
        const data = await response.json();
        if (isMounted) setUser(data);
      } catch (error) {
        if (isMounted) console.error(error);
      } finally {
        if (isMounted) setLoading(false);
      }
    };
    
    fetchUser();
    
    return () => {
      isMounted = false; // Prevent state update after unmount
    };
  }, [userId]); // Re-fetch when userId changes
  
  if (loading) return <div>Loading...</div>;
  return <div>{user?.name}</div>;
};

// Timer with cleanup
const Timer = () => {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
    
    return () => clearInterval(interval); // Cleanup
  }, []); // Empty deps - mount only
  
  return <div>{seconds}s</div>;
};

// Event listener with cleanup
const WindowSize = () => {
  const [size, setSize] = useState({ 
    width: window.innerWidth, 
    height: window.innerHeight 
  });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []); // No dependencies
  
  return <div>{size.width} x {size.height}</div>;
};

// Multiple effects for separation of concerns
const ChatRoom = ({ roomId }) => {
  const [messages, setMessages] = useState([]);
  
  // Effect 1: Connect to chat
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
  
  // Effect 2: Load message history
  useEffect(() => {
    loadMessages(roomId).then(setMessages);
  }, [roomId]);
  
  // Effect 3: Update document title
  useEffect(() => {
    document.title = \`Chat: \${roomId}\`;
    return () => { document.title = 'App'; };
  }, [roomId]);
  
  return <div>{/* UI */}</div>;
};
Note: In React 18+, effects run twice in development (Strict Mode) to help catch bugs. Always implement cleanup functions properly.

2.3 useContext Hook for Context Consumption

Concept Syntax Description Use Case
useContext Hook const value = useContext(Context) Access context value in component Consume shared state
Context Creation createContext(defaultValue) Create context object Define context for provider
Provider Component <Context.Provider value={...}> Provide context value to children Share data down tree
Multiple Contexts useContext(Context1), useContext(Context2) Consume multiple contexts Separate concerns (theme, auth)
Default Value createContext(fallback) Value when no provider exists Testing, development defaults

Example: Context creation and consumption

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

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

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

// 3. Create custom hook for easier consumption
const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
};

// 4. Consume context in components
const Header = () => {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <header className={theme}>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'dark' : 'light'}
      </button>
    </header>
  );
};

const Content = () => {
  const { theme } = useTheme();
  return <div className={theme}>Content</div>;
};

// 5. App with provider
const App = () => (
  <ThemeProvider>
    <Header />
    <Content />
  </ThemeProvider>
);

// Multiple contexts example
const AuthContext = createContext(null);
const SettingsContext = createContext({});

const Dashboard = () => {
  const auth = useContext(AuthContext);
  const settings = useContext(SettingsContext);
  const theme = useContext(ThemeContext);
  
  return <div>{auth.user.name}</div>;
};

2.4 useReducer Hook for Complex State Logic

Pattern Syntax Description Use Case
Basic useReducer useReducer(reducer, initialState) Alternative to useState for complex state Multiple related state values
Reducer Function (state, action) => newState Pure function handling state updates Centralized state logic
Dispatch Action dispatch({ type, payload }) Trigger state update User interactions, events
Lazy Initialization useReducer(reducer, arg, init) Compute initial state lazily Expensive initialization
Action Types const ACTIONS = { ADD, UPDATE } Constants for action types Type safety, autocomplete
Immer Integration produce(state, draft => {}) Mutable-style updates with Immer Complex nested updates

Example: useReducer for complex state management

import { useReducer } from 'react';

// Action types
const ACTIONS = {
  ADD_TODO: 'add_todo',
  TOGGLE_TODO: 'toggle_todo',
  DELETE_TODO: 'delete_todo',
  SET_FILTER: 'set_filter'
};

// Reducer function
const todoReducer = (state, action) => {
  switch (action.type) {
    case ACTIONS.ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.payload,
          completed: false
        }]
      };
      
    case ACTIONS.TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
      
    case ACTIONS.DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter(t => t.id !== action.payload)
      };
      
    case ACTIONS.SET_FILTER:
      return { ...state, filter: action.payload };
      
    default:
      return state;
  }
};

// Component using useReducer
const TodoApp = () => {
  const initialState = {
    todos: [],
    filter: 'all'
  };
  
  const [state, dispatch] = useReducer(todoReducer, initialState);
  
  const addTodo = (text) => {
    dispatch({ type: ACTIONS.ADD_TODO, payload: text });
  };
  
  const toggleTodo = (id) => {
    dispatch({ type: ACTIONS.TOGGLE_TODO, payload: id });
  };
  
  const deleteTodo = (id) => {
    dispatch({ type: ACTIONS.DELETE_TODO, payload: id });
  };
  
  const filteredTodos = state.todos.filter(todo => {
    if (state.filter === 'active') return !todo.completed;
    if (state.filter === 'completed') return todo.completed;
    return true;
  });
  
  return (
    <div>
      <input onSubmit={(e) => {
        addTodo(e.target.value);
        e.target.value = '';
      }} />
      
      <ul>
        {filteredTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
            <button onClick={() => deleteTodo(todo.id)}>✕</button>
          </li>
        ))}
      </ul>
      
      <div>
        {['all', 'active', 'completed'].map(filter => (
          <button
            key={filter}
            onClick={() => dispatch({ 
              type: ACTIONS.SET_FILTER, 
              payload: filter 
            })}
          >
            {filter}
          </button>
        ))}
      </div>
    </div>
  );
};
Note: Use useReducer over useState when: (1) complex state logic, (2) multiple sub-values, (3) next state depends on previous state, (4) need to optimize performance with deep updates.

2.5 useMemo and useCallback Optimization Hooks

Hook Syntax Description Use Case
useMemo useMemo(() => computation, [deps]) Memoize computed values Expensive calculations
useCallback useCallback(() => {}, [deps]) Memoize function references Prevent child re-renders
Dependencies Array [dep1, dep2] Recompute when deps change Control memoization lifecycle
Empty Dependencies useMemo(() => val, []) Compute once, never update Constants, one-time calculations
Referential Equality React.memo() with callbacks Prevent unnecessary re-renders Optimized child components

Example: useMemo and useCallback optimization

import { useMemo, useCallback, memo } from 'react';

// useMemo for expensive computations
const DataTable = ({ data, filterText }) => {
  // Memoize filtered and sorted data
  const processedData = useMemo(() => {
    console.log('Processing data...'); // Only logs when deps change
    return data
      .filter(item => item.name.includes(filterText))
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [data, filterText]);
  
  return (
    <table>
      {processedData.map(item => (
        <tr key={item.id}><td>{item.name}</td></tr>
      ))}
    </table>
  );
};

// useCallback to memoize functions
const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  // Without useCallback - new function every render
  const handleClick = () => {
    console.log('Clicked');
  };
  
  // With useCallback - same function reference
  const handleClickMemo = useCallback(() => {
    console.log('Clicked', count);
  }, [count]); // Recreate when count changes
  
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      
      {/* Child re-renders when text changes without memo */}
      <ChildComponent onClick={handleClickMemo} />
      
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
    </div>
  );
};

// Memoized child component
const ChildComponent = memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click Me</button>;
});

// Complex example combining both
const SearchableList = ({ items }) => {
  const [query, setQuery] = useState('');
  const [sortOrder, setSortOrder] = useState('asc');
  
  // Memoize filtered items
  const filteredItems = useMemo(() => {
    return items.filter(item => 
      item.name.toLowerCase().includes(query.toLowerCase())
    );
  }, [items, query]);
  
  // Memoize sorted items
  const sortedItems = useMemo(() => {
    return [...filteredItems].sort((a, b) => {
      const compare = a.name.localeCompare(b.name);
      return sortOrder === 'asc' ? compare : -compare;
    });
  }, [filteredItems, sortOrder]);
  
  // Memoize callback
  const handleSort = useCallback(() => {
    setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
  }, []);
  
  return (
    <div>
      <input 
        value={query} 
        onChange={(e) => setQuery(e.target.value)} 
      />
      <button onClick={handleSort}>Sort</button>
      <List items={sortedItems} />
    </div>
  );
};
Warning: Don't overuse memoization! Only use useMemo and useCallback when: (1) expensive computations, (2) referential equality matters (props to memoized children), (3) profiling shows performance issues.

2.6 useRef Hook for DOM and Value References

Use Case Syntax Description Example
DOM Reference const ref = useRef(null) Access DOM element directly <input ref={ref} />
Mutable Value ref.current = value Store value that doesn't trigger re-render Previous state, timers
Access Element ref.current.focus() Call DOM methods imperatively Focus, scroll, measure
Instance Variable useRef(initialValue) Persist value across renders Intervals, subscriptions
Previous Value useRef() + useEffect Track previous prop/state value Compare changes
Ref Callback ref={(node) => {}} Function called with DOM node Dynamic refs, measurements

Example: useRef for DOM access and mutable values

import { useRef, useEffect } from 'react';

// Focus input on mount
const AutoFocusInput = () => {
  const inputRef = useRef(null);
  
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  
  return <input ref={inputRef} />;
};

// Store timer ID without triggering re-renders
const Timer = () => {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  
  const start = () => {
    if (!intervalRef.current) {
      intervalRef.current = setInterval(() => {
        setCount(c => c + 1);
      }, 1000);
    }
  };
  
  const stop = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };
  
  useEffect(() => {
    return () => stop(); // Cleanup
  }, []);
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
};

// Track previous value
const usePrevious = (value) => {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  }, [value]);
  
  return ref.current;
};

const Counter = ({ count }) => {
  const prevCount = usePrevious(count);
  
  return (
    <div>
      Current: {count}, Previous: {prevCount}
    </div>
  );
};

// Measure element dimensions
const MeasureElement = () => {
  const divRef = useRef(null);
  const [dimensions, setDimensions] = useState({});
  
  useEffect(() => {
    if (divRef.current) {
      const { width, height } = divRef.current.getBoundingClientRect();
      setDimensions({ width, height });
    }
  }, []);
  
  return (
    <div ref={divRef}>
      Size: {dimensions.width} x {dimensions.height}
    </div>
  );
};

// Ref callback for dynamic refs
const DynamicList = ({ items }) => {
  const itemRefs = useRef(new Map());
  
  const scrollToItem = (id) => {
    const node = itemRefs.current.get(id);
    node?.scrollIntoView({ behavior: 'smooth' });
  };
  
  return (
    <ul>
      {items.map(item => (
        <li
          key={item.id}
          ref={(node) => {
            if (node) {
              itemRefs.current.set(item.id, node);
            } else {
              itemRefs.current.delete(item.id);
            }
          }}
        >
          {item.name}
        </li>
      ))}
    </ul>
  );
};
Note: useRef returns a mutable object whose .current property is initialized with the passed argument. The object persists for the component's lifetime. Changing .current doesn't cause re-renders.

3. Modern React 18+ Features REACT 18+

3.1 useTransition Hook for Non-blocking Updates NEW

Feature Syntax Description Use Case
useTransition Hook const [isPending, startTransition] = useTransition() Mark state updates as non-urgent transitions Keep UI responsive during updates
startTransition startTransition(() => {}) Wrap state updates to mark as transition Heavy computations, filtering
isPending Flag isPending ? 'Loading' : 'Done' Boolean indicating transition in progress Show loading indicators
Priority System Urgent vs transition updates React prioritizes urgent updates first Input responsiveness
Interruptible Renders React can pause transitions Allow urgent updates to interrupt Smooth user experience

Example: useTransition for responsive filtering

import { useState, useTransition } from 'react';

const SearchableList = ({ items }) => {
  const [query, setQuery] = useState('');
  const [filteredItems, setFilteredItems] = useState(items);
  const [isPending, startTransition] = useTransition();
  
  const handleSearch = (value) => {
    // Urgent: Update input immediately
    setQuery(value);
    
    // Non-urgent: Filter in background
    startTransition(() => {
      const filtered = items.filter(item =>
        item.name.toLowerCase().includes(value.toLowerCase())
      );
      setFilteredItems(filtered);
    });
  };
  
  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="Search..."
      />
      
      {isPending ? (
        <div>Updating results...</div>
      ) : (
        <ul>
          {filteredItems.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

// Tab switching example
const TabContainer = () => {
  const [activeTab, setActiveTab] = useState('tab1');
  const [isPending, startTransition] = useTransition();
  
  const switchTab = (tab) => {
    startTransition(() => {
      setActiveTab(tab);
    });
  };
  
  return (
    <div>
      <div className={isPending ? 'loading' : ''}>
        <button onClick={() => switchTab('tab1')}>Tab 1</button>
        <button onClick={() => switchTab('tab2')}>Tab 2</button>
        <button onClick={() => switchTab('tab3')}>Tab 3</button>
      </div>
      
      <TabContent tab={activeTab} />
    </div>
  );
};
Note: useTransition enables concurrent rendering. Transition updates can be interrupted by more urgent updates, keeping the UI responsive even during expensive operations.

3.2 useDeferredValue Hook for Deferred Values NEW

Feature Syntax Description Use Case
useDeferredValue const deferred = useDeferredValue(value) Defer updating a value to keep UI responsive Expensive child component updates
Deferred Rendering <Component value={deferred} /> Pass deferred value to expensive component Debounce-like behavior
Initial Value useDeferredValue(value, initialValue) Optional initial value during SSR Server-side rendering
Value Comparison value !== deferred Check if deferral is in progress Show loading states
import { useState, useDeferredValue, memo } from 'react';

const SearchResults = ({ query }) => {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  
  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <ExpensiveList query={deferredQuery} />
    </div>
  );
};

// Expensive component that benefits from deferral
const ExpensiveList = memo(({ query }) => {
  const items = useMemo(() => {
    // Expensive filtering/computation
    return largeDataset.filter(item =>
      item.name.toLowerCase().includes(query.toLowerCase())
    );
  }, [query]);
  
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

// Complete search component
const SearchPage = () => {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isSearching = query !== deferredQuery;
  
  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      
      {isSearching && <span>Searching...</span>}
      
      <SearchResults query={deferredQuery} />
    </div>
  );
};

// Comparison: useTransition vs useDeferredValue
// useTransition: You control the state update
// useDeferredValue: React controls when to update the value
Note: Use useDeferredValue when you can't control the state update (props from parent). Use useTransition when you control the state update.

3.3 useId Hook for Unique Identifiers NEW

Feature Syntax Description Use Case
useId Hook const id = useId() Generate unique ID for accessibility attributes Form labels, ARIA attributes
SSR Safe Same ID on client and server Consistent IDs across hydration Avoid hydration mismatches
Multiple IDs id + '-label', id + '-error' Derive multiple IDs from single hook Related elements
List Items Don't use for key prop Keys should be from data, not generated Use stable data IDs

Example: useId for accessible form fields

import { useId } from 'react';

// Basic form field with unique ID
const TextField = ({ label, error }) => {
  const id = useId();
  const errorId = id + '-error';
  
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input
        id={id}
        type="text"
        aria-describedby={error ? errorId : undefined}
        aria-invalid={error ? 'true' : 'false'}
      />
      {error && (
        <span id={errorId} role="alert">
          {error}
        </span>
      )}
    </div>
  );
};

// Complex form with multiple fields
const ContactForm = () => {
  const id = useId();
  
  return (
    <form>
      <div>
        <label htmlFor={id + '-name'}>Name</label>
        <input id={id + '-name'} type="text" />
      </div>
      
      <div>
        <label htmlFor={id + '-email'}>Email</label>
        <input id={id + '-email'} type="email" />
      </div>
      
      <div>
        <label htmlFor={id + '-message'}>Message</label>
        <textarea id={id + '-message'} />
      </div>
    </form>
  );
};

// Reusable input component
const Input = ({ label, type = 'text', ...props }) => {
  const id = useId();
  
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} type={type} {...props} />
    </>
  );
};

// ARIA example
const Accordion = ({ title, children }) => {
  const [isOpen, setIsOpen] = useState(false);
  const id = useId();
  const headerId = id + '-header';
  const panelId = id + '-panel';
  
  return (
    <div>
      <button
        id={headerId}
        aria-expanded={isOpen}
        aria-controls={panelId}
        onClick={() => setIsOpen(!isOpen)}
      >
        {title}
      </button>
      
      <div
        id={panelId}
        role="region"
        aria-labelledby={headerId}
        hidden={!isOpen}
      >
        {children}
      </div>
    </div>
  );
};
Warning: Don't use useId to generate keys in a list. Keys should come from your data. Use useId only for accessibility attributes like id, htmlFor, aria-*.

3.4 useSyncExternalStore Hook for External State NEW

Feature Syntax Description Use Case
useSyncExternalStore useSyncExternalStore(subscribe, getSnapshot) Subscribe to external store Redux, Zustand, browser APIs
Subscribe Function subscribe(callback) Subscribe to store changes Return unsubscribe function
getSnapshot Function getSnapshot() Get current store value Must return immutable value
SSR getSnapshot useSyncExternalStore(..., getServerSnapshot) Server-side initial value Hydration consistency
Tearing Prevention Consistent state across components No visual inconsistencies Concurrent rendering safety

Example: useSyncExternalStore for browser APIs

import { useSyncExternalStore } from 'react';

// Browser online status
const useOnlineStatus = () => {
  return useSyncExternalStore(
    // Subscribe function
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    // getSnapshot function
    () => navigator.onLine,
    // getServerSnapshot (SSR)
    () => true
  );
};

// Usage
const StatusIndicator = () => {
  const isOnline = useOnlineStatus();
  
  return (
    <div>
      {isOnline ? '🟢 Online' : '🔴 Offline'}
    </div>
  );
};

// Window width hook
const useWindowWidth = () => {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => window.innerWidth,
    () => 0 // SSR default
  );
};

// Custom store example
class CountStore {
  constructor() {
    this.count = 0;
    this.listeners = new Set();
  }
  
  subscribe = (callback) => {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback);
  };
  
  getSnapshot = () => {
    return this.count;
  };
  
  increment = () => {
    this.count++;
    this.listeners.forEach(listener => listener());
  };
}

const store = new CountStore();

const useCountStore = () => {
  return useSyncExternalStore(
    store.subscribe,
    store.getSnapshot
  );
};

const Counter = () => {
  const count = useCountStore();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => store.increment()}>+</button>
    </div>
  );
};

// Local storage sync
const useLocalStorage = (key, initialValue) => {
  return useSyncExternalStore(
    (callback) => {
      const handler = (e) => {
        if (e.key === key) callback();
      };
      window.addEventListener('storage', handler);
      return () => window.removeEventListener('storage', handler);
    },
    () => {
      try {
        return JSON.parse(localStorage.getItem(key)) ?? initialValue;
      } catch {
        return initialValue;
      }
    },
    () => initialValue
  );
};
Note: useSyncExternalStore is primarily for library authors. Most apps should use built-in hooks or state management libraries that already use this hook internally.

3.5 useInsertionEffect Hook for CSS-in-JS Libraries NEW

Feature Syntax Description Use Case
useInsertionEffect useInsertionEffect(() => {}, [deps]) Insert styles before layout effects CSS-in-JS libraries only
Timing Before useLayoutEffect Runs before DOM mutations are visible Inject <style> tags
No DOM Access Cannot read/write DOM DOM not yet updated Style injection only
Library Use Only Not for application code Advanced library feature styled-components, emotion

Example: useInsertionEffect for style injection

import { useInsertionEffect } from 'react';

// CSS-in-JS library example (simplified)
const styleCache = new Map();

const useStyleInjection = (css, id) => {
  useInsertionEffect(() => {
    // Check if style already injected
    if (styleCache.has(id)) {
      return;
    }
    
    // Create and inject style tag
    const style = document.createElement('style');
    style.textContent = css;
    style.setAttribute('data-style-id', id);
    document.head.appendChild(style);
    
    // Cache it
    styleCache.set(id, style);
    
    // Cleanup
    return () => {
      document.head.removeChild(style);
      styleCache.delete(id);
    };
  }, [css, id]);
};

// Example usage in a CSS-in-JS library
const useStyledComponent = (styles) => {
  const id = useId();
  const className = \`styled-\${id}\`;
  
  const css = \`.styled-\${id} {
    \${Object.entries(styles)
      .map(([key, value]) => \`\${key}: \${value};\`)
      .join('\n')}
  }\`;
  
  useStyleInjection(css, id);
  
  return className;
};

// Usage in components
const MyComponent = () => {
  const className = useStyledComponent({
    'background-color': '#f0f0f0',
    'padding': '20px',
    'border-radius': '8px'
  });
  
  return <div className={className}>Styled Content</div>;
};

// Real-world pattern (styled-components-like)
const styled = (tag) => (styles) => {
  return (props) => {
    const id = useId();
    const className = \`sc-\${id}\`;
    
    useInsertionEffect(() => {
      const css = generateCSS(className, styles, props);
      injectStyle(css, id);
      
      return () => removeStyle(id);
    }, [className, styles, props]);
    
    return React.createElement(tag, {
      ...props,
      className: \`\${className} \${props.className || ''}\`
    });
  };
};

// Usage
const Button = styled('button')\`
  background: blue;
  color: white;
  padding: 10px 20px;
\`;

// In component
<Button>Click Me</Button>
Warning: useInsertionEffect is ONLY for CSS-in-JS library authors. Regular applications should never use this hook. Use useEffect or useLayoutEffect instead.

3.6 Concurrent Features and Automatic Batching NEW

Feature Description Benefit Example
Automatic Batching Multiple state updates batched automatically Fewer renders, better performance Event handlers, timeouts, promises
Concurrent Rendering React can interrupt rendering work Keep UI responsive during updates Large list updates
Transitions Mark updates as non-urgent Prioritize urgent updates useTransition, startTransition
Streaming SSR Send HTML in chunks as ready Faster Time to First Byte Suspense on server
Selective Hydration Hydrate components as user interacts Faster Time to Interactive Priority-based hydration
Strict Mode Double Render Effects run twice in development Catch side effect bugs early Development only

Example: Automatic batching in React 18

import { useState } from 'react';
import { flushSync } from 'react-dom';

// React 18: Automatic batching everywhere
const AutoBatchingExample = () => {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  console.log('Render'); // Only logs once per click
  
  // Event handlers - batched (React 17 too)
  const handleClick = () => {
    setCount(c => c + 1);
    setFlag(f => !f);
    // Only 1 render
  };
  
  // Timeouts - NOW batched (was 2 renders in React 17)
  const handleTimeout = () => {
    setTimeout(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // Only 1 render in React 18!
    }, 1000);
  };
  
  // Promises - NOW batched (was 2 renders in React 17)
  const handleAsync = async () => {
    const data = await fetchData();
    setCount(data.count);
    setFlag(data.flag);
    // Only 1 render in React 18!
  };
  
  // Native event handlers - NOW batched
  useEffect(() => {
    const handler = () => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // Only 1 render in React 18!
    };
    
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);
  
  // Opt-out of batching with flushSync (rare)
  const handleNoFlush = () => {
    flushSync(() => {
      setCount(c => c + 1);
    });
    // React renders here
    flushSync(() => {
      setFlag(f => !f);
    });
    // React renders again - 2 total renders
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Flag: {flag.toString()}</p>
      <button onClick={handleClick}>Update</button>
      <button onClick={handleTimeout}>Update (Timeout)</button>
      <button onClick={handleAsync}>Update (Async)</button>
    </div>
  );
};

// Concurrent features example
const ConcurrentExample = () => {
  const [resource, setResource] = useState(initialResource);
  
  // Wrap in transition for smooth updates
  const handleUpdate = () => {
    startTransition(() => {
      setResource(fetchNewResource());
    });
  };
  
  return (
    <Suspense fallback={<Spinner />}>
      <button onClick={handleUpdate}>Update</button>
      <ResourceComponent resource={resource} />
    </Suspense>
  );
};

// Strict Mode double render
const StrictModeExample = () => {
  useEffect(() => {
    console.log('Effect');
    // In development, logs twice to help find bugs
    
    return () => {
      console.log('Cleanup');
      // Also runs twice in development
    };
  }, []);
  
  return <div>Component</div>;
};

React 18 Key Improvements

  • Automatic Batching: Multiple state updates batched everywhere (promises, timeouts, native events)
  • Concurrent Rendering: React can pause and resume work, keeping UI responsive
  • useTransition: Mark updates as non-urgent for better UX
  • useDeferredValue: Defer expensive updates while keeping input responsive
  • Streaming SSR: Send HTML in chunks for faster initial load
  • Selective Hydration: Prioritize hydration based on user interaction
Note: React 18's concurrent features are opt-in. Use createRoot instead of render to enable them: ReactDOM.createRoot(container).render(<App />)

4. Component State Management Patterns

4.1 Local State with useState and useReducer

Pattern When to Use Pros Cons
useState Simple independent state values Simple API, easy to understand, minimal boilerplate Can get messy with complex state
useReducer Complex state logic, multiple sub-values Centralized logic, predictable updates, testable More boilerplate, steeper learning curve
Multiple useState Unrelated state pieces Clear separation of concerns Many setters, potential for mistakes
Object State Related properties that update together Single state object, atomic updates Must spread previous state carefully
State Initializer Expensive initial computation Computed only once Function call syntax needed

Example: Choosing between useState and useReducer

// useState for simple state
const SimpleCounter = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
};

// Multiple useState for independent values
const UserForm = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  
  // Each state is independent and clear
  return (/* form */);
};

// useState with object for related data
const ProfileEditor = () => {
  const [profile, setProfile] = useState({
    name: '',
    email: '',
    bio: ''
  });
  
  const updateField = (field, value) => {
    setProfile(prev => ({ ...prev, [field]: value }));
  };
  
  return (/* editor */);
};

// useReducer for complex state logic
const ShoppingCart = () => {
  const [state, dispatch] = useReducer(cartReducer, {
    items: [],
    total: 0,
    discount: 0,
    tax: 0
  });
  
  return (
    <div>
      <button onClick={() => dispatch({ 
        type: 'ADD_ITEM', 
        payload: item 
      })}>
        Add to Cart
      </button>
      <button onClick={() => dispatch({ type: 'APPLY_DISCOUNT' })}>
        Apply Discount
      </button>
      <button onClick={() => dispatch({ type: 'CALCULATE_TOTAL' })}>
        Calculate Total
      </button>
    </div>
  );
};

// Decision tree:
// - 1 value, simple updates? → useState
// - Few independent values? → multiple useState
// - Related values updating together? → useState with object
// - Complex logic, state depends on actions? → useReducer
// - State transitions like a state machine? → useReducer

4.2 State Lifting and Sharing Between Components

Technique Description Use Case Trade-off
Lift State Up Move state to common parent component Share state between sibling components Parent re-renders on every update
Prop Drilling Pass state through component tree Simple component hierarchies Tedious with deep nesting
Inverse Data Flow Child updates parent via callbacks Child needs to modify parent state Callback props everywhere
Composition Pass components as children/props Avoid drilling through wrappers Requires planning component structure
Context Global state without prop drilling Deep nesting, many consumers Can cause unnecessary re-renders

Example: State lifting patterns

// Before: State in child components (not shared)
const TemperatureInput1 = () => {
  const [temp, setTemp] = useState('');
  return <input value={temp} onChange={(e) => setTemp(e.target.value)} />;
};

// After: Lift state to parent
const TemperatureConverter = () => {
  const [celsius, setCelsius] = useState('');
  
  const fahrenheit = celsius ? (celsius * 9/5 + 32).toFixed(1) : '';
  
  return (
    <div>
      <TemperatureInput
        scale="Celsius"
        temperature={celsius}
        onTemperatureChange={setCelsius}
      />
      <TemperatureInput
        scale="Fahrenheit"
        temperature={fahrenheit}
        onTemperatureChange={(f) => setCelsius(((f - 32) * 5/9).toFixed(1))}
      />
      <p>Boiling: {celsius >= 100 ? 'Yes' : 'No'}</p>
    </div>
  );
};

const TemperatureInput = ({ scale, temperature, onTemperatureChange }) => (
  <div>
    <label>{scale}:</label>
    <input
      value={temperature}
      onChange={(e) => onTemperatureChange(e.target.value)}
    />
  </div>
);

// Composition to avoid prop drilling
const Layout = ({ header, sidebar, content }) => (
  <div>
    <header>{header}</header>
    <div className="main">
      <aside>{sidebar}</aside>
      <main>{content}</main>
    </div>
  </div>
);

// Usage: Pass components with their own state
const App = () => {
  const [user, setUser] = useState(null);
  
  return (
    <Layout
      header={<Header user={user} onLogout={() => setUser(null)} />}
      sidebar={<Sidebar />} {/* No user prop needed */}
      content={<Dashboard user={user} />}
    />
  );
};

// When to lift state:
// ✓ Two+ components need same data
// ✓ Data needs to be synchronized
// ✓ Parent needs to coordinate children
// ✗ Only one component uses it (keep local)
// ✗ Performance issues (consider Context/memo)

4.3 State Colocation and Component Responsibility

Principle Description Benefit Example
State Colocation Keep state as close to where it's used Better performance, easier maintenance Modal open state in Modal component
Single Responsibility Component manages its own concerns Reusable, testable, maintainable Form validates its own fields
Lift on Demand Start local, lift only when needed Avoid premature optimization Lift when sibling needs access
Encapsulation Hide implementation details Flexibility to change internals Accordion manages panel states
Separation of Concerns Split unrelated state into components Independent testing and changes Filter state vs display state

Example: State colocation best practices

// ❌ Bad: State too high in tree
const App = () => {
  const [modalOpen, setModalOpen] = useState(false);
  const [tooltipOpen, setTooltipOpen] = useState(false);
  const [accordionIndex, setAccordionIndex] = useState(0);
  
  return (
    <div>
      <Modal open={modalOpen} onClose={() => setModalOpen(false)} />
      <Tooltip open={tooltipOpen} />
      <Accordion activeIndex={accordionIndex} />
    </div>
  );
};

// ✓ Good: State colocated with components
const Modal = () => {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open</button>
      {isOpen && (
        <div className="modal">
          <button onClick={() => setIsOpen(false)}>Close</button>
        </div>
      )}
    </>
  );
};

// ✓ Good: Component responsibility
const SearchableList = ({ items }) => {
  // Search state belongs here
  const [query, setQuery] = useState('');
  const [sortOrder, setSortOrder] = useState('asc');
  
  const filteredItems = useMemo(() => {
    return items
      .filter(item => item.name.includes(query))
      .sort((a, b) => sortOrder === 'asc' 
        ? a.name.localeCompare(b.name)
        : b.name.localeCompare(a.name)
      );
  }, [items, query, sortOrder]);
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => setSortOrder(prev => 
        prev === 'asc' ? 'desc' : 'asc'
      )}>
        Sort
      </button>
      <List items={filteredItems} />
    </div>
  );
};

// ✓ Good: Lift only when necessary
const ProductPage = () => {
  // Shared between multiple components
  const [selectedProduct, setSelectedProduct] = useState(null);
  
  return (
    <div>
      <ProductList onSelect={setSelectedProduct} />
      <ProductDetails product={selectedProduct} />
      <RelatedProducts productId={selectedProduct?.id} />
    </div>
  );
};

// Guidelines:
// 1. Start with local state
// 2. Lift when siblings need to share
// 3. Keep state close to usage
// 4. Each component owns its UI state
Note: State colocation improves performance because only components that need the state will re-render. Avoid lifting state too early—do it when actually needed.

4.4 Derived State and Computed Values

Technique Implementation Use Case Performance
Inline Calculation const total = items.reduce(...) Simple, cheap calculations Recalculated every render
useMemo useMemo(() => calc, [deps]) Expensive computations Cached between renders
Avoid Sync State Don't copy props to state Prevent sync issues No duplication overhead
Controlled Derivation Derive in render, don't store Always in sync with source Depends on calculation cost
Key Reset Pattern <Comp key={id} /> Reset state when prop changes Component remounts

Example: Derived state patterns

// ✓ Good: Derive inline (cheap calculation)
const CartSummary = ({ items }) => {
  const total = items.reduce((sum, item) => sum + item.price, 0);
  const itemCount = items.length;
  const tax = total * 0.1;
  
  return (
    <div>
      <p>Items: {itemCount}</p>
      <p>Subtotal: ${total}</p>
      <p>Tax: ${tax}</p>
      <p>Total: ${total + tax}</p>
    </div>
  );
};

// ✓ Good: useMemo for expensive calculations
const DataTable = ({ data, filters }) => {
  const filteredData = useMemo(() => {
    return data
      .filter(row => filters.every(f => f(row)))
      .sort((a, b) => a.name.localeCompare(b.name))
      .map(row => transformRow(row));
  }, [data, filters]);
  
  return <table>{/* render filteredData */}</table>;
};

// ❌ Bad: Syncing props to state
const SearchInput = ({ initialValue }) => {
  // DON'T DO THIS
  const [value, setValue] = useState(initialValue);
  // value won't update when initialValue changes!
  
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
};

// ✓ Good: Controlled component
const SearchInput = ({ value, onChange }) => {
  return <input value={value} onChange={onChange} />;
};

// ✓ Good: Key reset pattern when prop changes
const UserProfile = ({ userId }) => {
  return <ProfileForm key={userId} userId={userId} />;
  // ProfileForm remounts and resets state when userId changes
};

// ❌ Bad: Storing derived state
const FilteredList = ({ items, filter }) => {
  const [filteredItems, setFilteredItems] = useState([]);
  
  // This creates sync issues!
  useEffect(() => {
    setFilteredItems(items.filter(filter));
  }, [items, filter]);
  
  return <List items={filteredItems} />;
};

// ✓ Good: Derive on every render
const FilteredList = ({ items, filter }) => {
  const filteredItems = useMemo(
    () => items.filter(filter),
    [items, filter]
  );
  
  return <List items={filteredItems} />;
};

// Rule: If you can calculate it from props/state, don't store it!
Warning: Never copy props to state unless you explicitly want to ignore prop updates. This causes sync bugs. Derive values instead or use the key prop to reset component state.

4.5 State Normalization for Complex Data

Pattern Structure Benefit Use Case
Normalized State { byId: {}, allIds: [] } O(1) lookups, no duplication Large datasets, frequent updates
Flat Structure Avoid deep nesting Easier updates, better performance Nested entities
Entity Tables Separate entities by type Clear organization, type safety Multiple entity types
Reference by ID Store IDs instead of objects Single source of truth Relationships between entities
Selector Functions Compute derived views Reusable query logic Complex filtering/sorting

Example: State normalization patterns (Part 1/2)

// ❌ Bad: Nested, duplicated data
const badState = {
  posts: [
    {
      id: 1,
      title: 'Post 1',
      author: { id: 1, name: 'Alice' },
      comments: [
        { id: 1, text: 'Comment', author: { id: 2, name: 'Bob' } }
      ]
    }
  ]
};
// Problems: Deep nesting, author duplicated, hard to update

// ✓ Good: Normalized structure
const normalizedState = {
  users: {
    byId: {
      1: { id: 1, name: 'Alice' },
      2: { id: 2, name: 'Bob' }
    },
    allIds: [1, 2]
  },
  posts: {
    byId: {
      1: { id: 1, title: 'Post 1', authorId: 1, commentIds: [1] }
    },
    allIds: [1]
  },
  comments: {
    byId: {
      1: { id: 1, text: 'Comment', authorId: 2, postId: 1 }
    },
    allIds: [1]
  }
};

// Reducer for normalized state
const postsReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_POST':
      return {
        ...state,
        byId: {
          ...state.byId,
          [action.payload.id]: action.payload
        },
        allIds: [...state.allIds, action.payload.id]
      };
      
    case 'UPDATE_POST':
      return {
        ...state,
        byId: {
          ...state.byId,
          [action.payload.id]: {
            ...state.byId[action.payload.id],
            ...action.payload.updates
          }
        }
      };
      
    case 'DELETE_POST':
      const { [action.payload]: deleted, ...remainingById } = state.byId;
      return {
        byId: remainingById,
        allIds: state.allIds.filter(id => id !== action.payload)
      };
      
    default:
      return state;
  }
};

Example: State normalization patterns (Part 2/2)

// Selector functions
const selectPostById = (state, postId) => state.posts.byId[postId];

const selectPostWithAuthor = (state, postId) => {
  const post = state.posts.byId[postId];
  const author = state.users.byId[post.authorId];
  return { ...post, author };
};

const selectPostsWithComments = (state) => {
  return state.posts.allIds.map(postId => {
    const post = state.posts.byId[postId];
    const comments = post.commentIds.map(
      commentId => state.comments.byId[commentId]
    );
    return { ...post, comments };
  });
};

// Component usage
const PostList = () => {
  const [state, dispatch] = useReducer(postsReducer, initialState);
  
  const posts = state.posts.allIds.map(id => state.posts.byId[id]);
  
  return (
    <div>
      {posts.map(post => (
        <Post
          key={post.id}
          post={post}
          author={state.users.byId[post.authorId]}
          onUpdate={(updates) => dispatch({
            type: 'UPDATE_POST',
            payload: { id: post.id, updates }
          })}
        />
      ))}
    </div>
  );
};

// Benefits of normalization:
// ✓ Single source of truth
// ✓ Fast lookups by ID
// ✓ Easy updates (no searching)
// ✓ No data duplication
// ✓ Better performance
Note: Consider normalization when you have: (1) nested entities, (2) the same entity in multiple places, (3) frequent updates to entities, (4) need for fast lookups. Libraries like normalizr can help.

4.6 State Machines and useReducer Patterns

Concept Description Benefit Example
State Machine Finite set of states and transitions Predictable, testable state logic Loading, success, error states
Valid Transitions Only allow specific state changes Prevent impossible states Can't be loading and success
Action-based Updates Dispatch actions to trigger transitions Clear intent, easier debugging FETCH, SUCCESS, ERROR actions
State Context Additional data within each state Rich state information Error message in error state
XState Integration Formal state machine library Visualization, advanced features Complex workflows

Example: State machine patterns with useReducer (Part 1/2)

// State machine for data fetching
const STATES = {
  IDLE: 'idle',
  LOADING: 'loading',
  SUCCESS: 'success',
  ERROR: 'error'
};

const fetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH':
      return { status: STATES.LOADING, data: null, error: null };
      
    case 'SUCCESS':
      return { status: STATES.SUCCESS, data: action.payload, error: null };
      
    case 'ERROR':
      return { status: STATES.ERROR, data: null, error: action.payload };
      
    case 'RESET':
      return { status: STATES.IDLE, data: null, error: null };
      
    default:
      return state;
  }
};

const DataFetcher = ({ url }) => {
  const [state, dispatch] = useReducer(fetchReducer, {
    status: STATES.IDLE,
    data: null,
    error: null
  });
  
  const fetchData = async () => {
    dispatch({ type: 'FETCH' });
    try {
      const response = await fetch(url);
      const data = await response.json();
      dispatch({ type: 'SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'ERROR', payload: error.message });
    }
  };
  
  // Render based on state
  if (state.status === STATES.IDLE) {
    return <button onClick={fetchData}>Load Data</button>;
  }
  
  if (state.status === STATES.LOADING) {
    return <div>Loading...</div>;
  }
  
  if (state.status === STATES.ERROR) {
    return (
      <div>
        Error: {state.error}
        <button onClick={fetchData}>Retry</button>
      </div>
    );
  }
  
  return (
    <div>
      <pre>{JSON.stringify(state.data, null, 2)}</pre>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
};

Example: State machine patterns with useReducer (Part 2/2)

// Complex state machine: Form wizard
const WIZARD_STATES = {
  PERSONAL_INFO: 'personalInfo',
  ADDRESS: 'address',
  PAYMENT: 'payment',
  REVIEW: 'review',
  SUBMITTING: 'submitting',
  SUCCESS: 'success',
  ERROR: 'error'
};

const wizardReducer = (state, action) => {
  switch (action.type) {
    case 'NEXT':
      const nextStates = {
        [WIZARD_STATES.PERSONAL_INFO]: WIZARD_STATES.ADDRESS,
        [WIZARD_STATES.ADDRESS]: WIZARD_STATES.PAYMENT,
        [WIZARD_STATES.PAYMENT]: WIZARD_STATES.REVIEW
      };
      return { ...state, currentStep: nextStates[state.currentStep] };
      
    case 'BACK':
      const prevStates = {
        [WIZARD_STATES.ADDRESS]: WIZARD_STATES.PERSONAL_INFO,
        [WIZARD_STATES.PAYMENT]: WIZARD_STATES.ADDRESS,
        [WIZARD_STATES.REVIEW]: WIZARD_STATES.PAYMENT
      };
      return { ...state, currentStep: prevStates[state.currentStep] };
      
    case 'UPDATE_DATA':
      return {
        ...state,
        formData: { ...state.formData, ...action.payload }
      };
      
    case 'SUBMIT':
      return { ...state, currentStep: WIZARD_STATES.SUBMITTING };
      
    case 'SUBMIT_SUCCESS':
      return { ...state, currentStep: WIZARD_STATES.SUCCESS };
      
    case 'SUBMIT_ERROR':
      return { 
        ...state, 
        currentStep: WIZARD_STATES.ERROR,
        error: action.payload
      };
      
    default:
      return state;
  }
};

// Benefits of state machines:
// ✓ Impossible states become impossible
// ✓ Clear transitions between states
// ✓ Easy to test each state
// ✓ Visual representation possible
// ✓ Predictable behavior

State Management Decision Guide

  • Local component state: Use useState for simple values, useReducer for complex logic
  • Sharing state: Lift to common parent, use composition to avoid prop drilling
  • State location: Keep state close to where it's used (colocation)
  • Derived values: Calculate from existing state, don't duplicate
  • Complex data: Normalize structure with byId/allIds pattern
  • State machines: Use for well-defined state transitions and workflows

5. React Context API and Global State

5.1 createContext and Provider Components

Feature Syntax Description Use Case
createContext const Ctx = createContext(defaultValue) Create context object Define shared state container
Default Value createContext(initialValue) Fallback when no provider exists Testing, development defaults
Provider Component <Ctx.Provider value={...}> Provide context value to children Share data down component tree
Provider Value value={{ state, actions }} Object with state and updater functions Complete state management
Nested Providers <A><B><C /></B></A> Stack multiple providers Compose contexts
Provider Pattern Wrap App with providers Make context available globally App-wide state

Example: Creating and providing context

import { createContext, useState } from 'react';

// 1. Create context with default value
const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {}
});

// 2. Create provider component
export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const value = {
    theme,
    toggleTheme
  };
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

// 3. Create auth provider
const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  const login = async (credentials) => {
    setLoading(true);
    const userData = await api.login(credentials);
    setUser(userData);
    setLoading(false);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

// 4. Compose providers in App
const App = () => (
  <ThemeProvider>
    <AuthProvider>
      <Router>
        <Layout />
      </Router>
    </AuthProvider>
  </ThemeProvider>
);

// 5. Provider composition helper
const ComposeProviders = ({ providers, children }) => {
  return providers.reduceRight(
    (acc, Provider) => <Provider>{acc}</Provider>,
    children
  );
};

// Usage
<ComposeProviders providers={[ThemeProvider, AuthProvider, RouterProvider]}>
  <App />
</ComposeProviders>

5.2 useContext Hook and Context Consumption

Pattern Syntax Description Best Practice
useContext Hook const value = useContext(Context) Access context value in component Must be inside Provider
Destructure Value const { state, action } = useContext(Ctx) Extract specific values from context Cleaner code, clear dependencies
Custom Hook const useMyContext = () => useContext(Ctx) Wrap useContext in custom hook Better error handling, abstraction
Null Check if (!context) throw Error Ensure provider exists Catch missing provider early
Multiple Contexts Call useContext multiple times Consume different contexts Separate concerns

Example: Consuming context with custom hooks

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

// Context setup
const UserContext = createContext(null);

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

// ✓ Good: Custom hook with error handling
export const useUser = () => {
  const context = useContext(UserContext);
  
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  
  return context;
};

// Usage in components
const Profile = () => {
  const { user, setUser } = useUser();
  
  if (!user) return <Login />;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={() => setUser(null)}>Logout</button>
    </div>
  );
};

// Multiple contexts example
const ThemeContext = createContext('light');
const LanguageContext = createContext('en');

const useTheme = () => useContext(ThemeContext);
const useLanguage = () => useContext(LanguageContext);

const Header = () => {
  const theme = useTheme();
  const language = useLanguage();
  const { user } = useUser();
  
  return (
    <header className={theme}>
      <span>{language === 'en' ? 'Welcome' : 'Bienvenido'}</span>
      {user && <span>{user.name}</span>}
    </header>
  );
};

// Selective context consumption
const Button = () => {
  // Only subscribes to theme, not user
  const theme = useTheme();
  return <button className={theme}>Click</button>;
};

// Pattern: Context + Reducer
const TodoContext = createContext(null);

export const TodoProvider = ({ children }) => {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  
  return (
    <TodoContext.Provider value={{ state, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
};

export const useTodos = () => {
  const context = useContext(TodoContext);
  if (!context) throw new Error('useTodos requires TodoProvider');
  return context;
};

5.3 Multiple Contexts and Context Composition

Pattern Description Benefit Example
Separate Contexts Different contexts for different concerns Clear separation, selective updates Theme, Auth, Language contexts
Nested Providers Stack providers in component tree Layer functionality Auth wraps Theme wraps App
Context Composition Combine multiple contexts in one hook Convenient API, related data useApp() returns theme + auth
Scoped Contexts Context for specific feature area Isolated state, no global pollution Form context, Modal context
Context Hierarchy Parent contexts provide to children Natural data flow App → Feature → Component

Example: Multiple contexts and composition

// Separate contexts for different concerns
const ThemeContext = createContext('light');
const AuthContext = createContext(null);
const NotificationContext = createContext(null);

// Provider setup
const App = () => (
  <ThemeProvider>
    <AuthProvider>
      <NotificationProvider>
        <Router />
      </NotificationProvider>
    </AuthProvider>
  </ThemeProvider>
);

// Composed hook for convenience
const useAppContext = () => {
  const theme = useContext(ThemeContext);
  const auth = useContext(AuthContext);
  const notifications = useContext(NotificationContext);
  
  return { theme, auth, notifications };
};

// Usage
const Dashboard = () => {
  const { theme, auth, notifications } = useAppContext();
  
  return (
    <div className={theme}>
      <h1>Welcome {auth.user.name}</h1>
      {notifications.messages.map(msg => (
        <div key={msg.id}>{msg.text}</div>
      ))}
    </div>
  );
};

// Scoped context for features
const FormContext = createContext(null);

const FormProvider = ({ children, initialValues }) => {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  
  const setValue = (field, value) => {
    setValues(prev => ({ ...prev, [field]: value }));
  };
  
  return (
    <FormContext.Provider value={{ values, errors, setValue }}>
      {children}
    </FormContext.Provider>
  );
};

// Form uses scoped context
const MyForm = () => (
  <FormProvider initialValues={{ name: '', email: '' }}>
    <FormFields />
    <SubmitButton />
  </FormProvider>
);

// Hierarchical contexts
const AppContext = createContext(null);
const FeatureContext = createContext(null);

const FeatureArea = () => {
  const appData = useContext(AppContext);
  const [featureState, setFeatureState] = useState({});
  
  return (
    <FeatureContext.Provider value={{ appData, featureState, setFeatureState }}>
      <FeatureComponents />
    </FeatureContext.Provider>
  );
};

// Provider composition utility
const combineProviders = (...providers) => {
  return ({ children }) => {
    return providers.reduce(
      (acc, [Provider, props]) => (
        <Provider {...props}>{acc}</Provider>
      ),
      children
    );
  };
};

// Usage
const AppProviders = combineProviders(
  [ThemeProvider, { defaultTheme: 'light' }],
  [AuthProvider, {}],
  [NotificationProvider, { position: 'top-right' }]
);

<AppProviders>
  <App />
</AppProviders>

5.4 Context Performance and Re-render Optimization

Issue Cause Solution Pattern
Unnecessary Re-renders Provider value changes on every render Memoize provider value useMemo(() => ({ state }), [state])
All Consumers Update Any context change triggers all consumers Split contexts by update frequency Separate read-only from mutable state
Large Context Objects Single context with many properties Multiple smaller contexts Theme, Auth, Settings as separate
Function References New functions created every render useCallback for updater functions useCallback(() => {}, [])
Deep Component Trees Many components between provider/consumer React.memo on intermediate components Prevent propagation of re-renders

Example: Context performance optimization

// ❌ Bad: New object every render
const BadProvider = ({ children }) => {
  const [count, setCount] = useState(0);
  
  // This creates a new object on every render!
  return (
    <Context.Provider value={{ count, setCount }}>
      {children}
    </Context.Provider>
  );
};

// ✓ Good: Memoized value
const GoodProvider = ({ children }) => {
  const [count, setCount] = useState(0);
  
  const value = useMemo(
    () => ({ count, setCount }),
    [count] // Only recreate when count changes
  );
  
  return (
    <Context.Provider value={value}>
      {children}
    </Context.Provider>
  );
};

// ✓ Better: Split contexts
const CountContext = createContext(0);
const CountDispatchContext = createContext(null);

const CountProvider = ({ children }) => {
  const [count, setCount] = useState(0);
  
  // State and dispatch are separate contexts
  return (
    <CountContext.Provider value={count}>
      <CountDispatchContext.Provider value={setCount}>
        {children}
      </CountDispatchContext.Provider>
    </CountContext.Provider>
  );
};

// Components can subscribe to only what they need
const DisplayCount = () => {
  const count = useContext(CountContext); // Only re-renders when count changes
  return <div>{count}</div>;
};

const IncrementButton = () => {
  const setCount = useContext(CountDispatchContext); // Never re-renders!
  return <button onClick={() => setCount(c => c + 1)}>+</button>;
};

// ✓ Good: Memoized callbacks
const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  const login = useCallback(async (credentials) => {
    const userData = await api.login(credentials);
    setUser(userData);
  }, []);
  
  const logout = useCallback(() => {
    setUser(null);
  }, []);
  
  const value = useMemo(
    () => ({ user, login, logout }),
    [user, login, logout]
  );
  
  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
};

// Memo intermediate components
const Layout = memo(({ children }) => {
  return (
    <div className="layout">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
});

// Split by update frequency
const StaticConfigContext = createContext(null); // Rarely changes
const DynamicDataContext = createContext(null);  // Changes often

const App = () => (
  <StaticConfigContext.Provider value={config}>
    <DynamicDataProvider>
      <Routes />
    </DynamicDataProvider>
  </StaticConfigContext.Provider>
);
Warning: Context triggers re-renders in ALL consumers when value changes. Use useMemo for provider values, useCallback for functions, and split contexts to minimize unnecessary updates.

5.5 Context vs Prop Drilling Trade-offs

Approach Pros Cons When to Use
Props Explicit, type-safe, easy to trace, testable Verbose with deep nesting, refactoring burden 2-3 levels deep, clear data flow needed
Context Avoid drilling, cleaner intermediate components Implicit dependencies, harder to trace, re-render issues Deep nesting, many consumers, global state
Composition Avoid both drilling and context, flexible Requires component structure planning Layout components, wrapper abstraction
State Management Powerful features, DevTools, middleware Boilerplate, learning curve, bundle size Complex apps, time-travel debugging
URL State Shareable, persistent, no drilling Limited to serializable data, URL length Filters, pagination, tabs

Example: Comparing approaches

// Approach 1: Props (explicit but verbose)
const App = () => {
  const [theme, setTheme] = useState('light');
  return <Layout theme={theme} setTheme={setTheme} />;
};

const Layout = ({ theme, setTheme }) => (
  <div>
    <Header theme={theme} setTheme={setTheme} />
    <Content theme={theme} />
  </div>
);

const Header = ({ theme, setTheme }) => (
  <header>
    <ThemeToggle theme={theme} setTheme={setTheme} />
  </header>
);

// Approach 2: Context (implicit but cleaner)
const ThemeContext = createContext('light');

const App = () => {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
};

const Layout = () => (
  <div>
    <Header />
    <Content />
  </div>
);

const ThemeToggle = () => {
  const { theme, setTheme } = useContext(ThemeContext);
  return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle</button>;
};

// Approach 3: Composition (flexible)
const App = () => {
  const [theme, setTheme] = useState('light');
  return (
    <Layout
      header={<Header theme={theme} setTheme={setTheme} />}
      content={<Content theme={theme} />}
    />
  );
};

const Layout = ({ header, content }) => (
  <div>
    {header}
    {content}
  </div>
);

// Decision guide:
// Props: 1-3 levels, clear data flow, explicit dependencies
// Context: 4+ levels, many consumers, truly global state
// Composition: Layout components, avoid wrapper drilling
// State library: Complex state, time-travel, middleware needed
Note: Start with props. Add context when drilling becomes painful (usually 4+ levels). Consider composition before reaching for context. Reserve state management libraries for truly complex apps.

5.6 Custom Context Hooks and Abstractions

Pattern Implementation Benefit Example
Custom Hook Wrap useContext in function Better error messages, validation useAuth() instead of useContext(AuthContext)
Error Handling Throw if provider missing Catch mistakes early Better DX, clear error messages
Selector Pattern Custom hooks for subsets of context Optimized updates, clearer API useUserName() only subscribes to name
Action Creators Provide functions not raw dispatch Type-safe, encapsulated logic login() instead of dispatch({ type: 'LOGIN' })
Computed Values Derive values in custom hooks Reusable logic, memoization useIsAuthenticated() derives from user state

Example: Custom context hooks and abstractions

// Advanced context pattern with custom hooks
const AuthContext = createContext(null);

// Provider with reducer
export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    loading: true,
    error: null
  });
  
  const value = useMemo(() => ({ state, dispatch }), [state]);
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
};

// Base hook with error handling
const useAuthContext = () => {
  const context = useContext(AuthContext);
  
  if (!context) {
    throw new Error('useAuth hooks must be used within AuthProvider');
  }
  
  return context;
};

// Public API - action creators
export const useAuth = () => {
  const { state, dispatch } = useAuthContext();
  
  const login = useCallback(async (credentials) => {
    dispatch({ type: 'LOGIN_START' });
    try {
      const user = await api.login(credentials);
      dispatch({ type: 'LOGIN_SUCCESS', payload: user });
    } catch (error) {
      dispatch({ type: 'LOGIN_ERROR', payload: error.message });
    }
  }, [dispatch]);
  
  const logout = useCallback(() => {
    dispatch({ type: 'LOGOUT' });
  }, [dispatch]);
  
  return {
    user: state.user,
    loading: state.loading,
    error: state.error,
    login,
    logout
  };
};

// Selector hooks for performance
export const useUser = () => {
  const { state } = useAuthContext();
  return state.user;
};

export const useIsAuthenticated = () => {
  const { state } = useAuthContext();
  return state.user !== null;
};

export const useAuthLoading = () => {
  const { state } = useAuthContext();
  return state.loading;
};

// Usage in components
const Profile = () => {
  const { user, logout } = useAuth();
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
};

const LoginButton = () => {
  const isAuthenticated = useIsAuthenticated();
  const { login } = useAuth();
  
  if (isAuthenticated) return null;
  
  return <button onClick={() => login({ email, password })}>Login</button>;
};

// Advanced: Factory for creating context hooks
const createContextHook = (Context, name) => {
  return () => {
    const context = useContext(Context);
    if (!context) {
      throw new Error(\`use\${name} must be used within \${name}Provider\`);
    }
    return context;
  };
};

// Usage
const useTheme = createContextHook(ThemeContext, 'Theme');
const useSettings = createContextHook(SettingsContext, 'Settings');

Context API Best Practices

  • Custom hooks: Always wrap useContext in custom hooks with error handling
  • Memoization: Use useMemo for provider values and useCallback for functions
  • Split contexts: Separate by concern and update frequency to optimize re-renders
  • Action creators: Provide high-level APIs, hide implementation details
  • Start simple: Use props first, context for deep drilling (4+ levels)
  • Composition over context: Consider component composition before context

6. Forms and User Input Handling

6.1 Controlled Components and Input State Management

Input Type Pattern State Binding Event Handler
Text Input <input value={state} onChange={handler} /> State holds current value e.target.value
Textarea <textarea value={state} onChange={handler} /> Same as text input e.target.value
Checkbox <input type="checkbox" checked={state} /> Boolean state e.target.checked
Radio <input type="radio" checked={state === value} /> Compare state to value e.target.value
Select <select value={state} onChange={handler} /> Selected option value e.target.value
Multiple Select <select multiple value={array} /> Array of values Array.from(e.target.selectedOptions)

Example: Controlled component patterns

// Basic text input
const TextInput = () => {
  const [value, setValue] = useState('');
  
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Enter text"
    />
  );
};

// Multiple inputs with single state object
const Form = () => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  return (
    <form>
      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        name="email"
        type="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="age"
        type="number"
        value={formData.age}
        onChange={handleChange}
        placeholder="Age"
      />
    </form>
  );
};

// Checkbox example
const CheckboxInput = () => {
  const [accepted, setAccepted] = useState(false);
  
  return (
    <label>
      <input
        type="checkbox"
        checked={accepted}
        onChange={(e) => setAccepted(e.target.checked)}
      />
      I accept the terms
    </label>
  );
};

// Multiple checkboxes (array)
const MultiCheckbox = () => {
  const [interests, setInterests] = useState([]);
  
  const handleToggle = (interest) => {
    setInterests(prev =>
      prev.includes(interest)
        ? prev.filter(i => i !== interest)
        : [...prev, interest]
    );
  };
  
  return (
    <div>
      {['React', 'Vue', 'Angular'].map(tech => (
        <label key={tech}>
          <input
            type="checkbox"
            checked={interests.includes(tech)}
            onChange={() => handleToggle(tech)}
          />
          {tech}
        </label>
      ))}
    </div>
  );
};

// Radio buttons
const RadioInput = () => {
  const [plan, setPlan] = useState('basic');
  
  return (
    <div>
      {['basic', 'pro', 'enterprise'].map(value => (
        <label key={value}>
          <input
            type="radio"
            name="plan"
            value={value}
            checked={plan === value}
            onChange={(e) => setPlan(e.target.value)}
          />
          {value}
        </label>
      ))}
    </div>
  );
};

// Select dropdown
const SelectInput = () => {
  const [country, setCountry] = useState('');
  
  return (
    <select value={country} onChange={(e) => setCountry(e.target.value)}>
      <option value="">Select country</option>
      <option value="us">United States</option>
      <option value="uk">United Kingdom</option>
      <option value="ca">Canada</option>
    </select>
  );
};
Note: Controlled components have React state as the "single source of truth". Every state mutation happens through setState, providing full control and predictability.

6.2 Uncontrolled Components and useRef

Approach Implementation When to Use Access Pattern
useRef const ref = useRef(null) Direct DOM access needed ref.current.value
defaultValue <input defaultValue="text" /> Initial value only No state synchronization
File Input Always uncontrolled Security reasons ref.current.files
Form Reset formRef.current.reset() Native form methods Reset all fields at once
Integration Third-party libraries Non-React code Direct DOM manipulation

Example: Uncontrolled components with useRef

import { useRef } from 'react';

// Basic uncontrolled input
const UncontrolledInput = () => {
  const inputRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Value:', inputRef.current.value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="Initial value" />
      <button type="submit">Submit</button>
    </form>
  );
};

// File input (always uncontrolled)
const FileInput = () => {
  const fileRef = useRef(null);
  
  const handleUpload = () => {
    const files = fileRef.current.files;
    if (files.length > 0) {
      console.log('Selected file:', files[0].name);
    }
  };
  
  return (
    <div>
      <input type="file" ref={fileRef} />
      <button onClick={handleUpload}>Upload</button>
    </div>
  );
};

// Form with multiple refs
const UncontrolledForm = () => {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  const formRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const formData = {
      name: nameRef.current.value,
      email: emailRef.current.value
    };
    
    console.log('Form data:', formData);
    
    // Reset form using native method
    formRef.current.reset();
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input ref={nameRef} name="name" defaultValue="" />
      <input ref={emailRef} name="email" type="email" defaultValue="" />
      <button type="submit">Submit</button>
      <button type="button" onClick={() => formRef.current.reset()}>
        Clear
      </button>
    </form>
  );
};

// Hybrid approach (controlled + ref for advanced features)
const HybridInput = () => {
  const [value, setValue] = useState('');
  const inputRef = useRef(null);
  
  const handleFocus = () => {
    inputRef.current.focus();
  };
  
  const handleSelectAll = () => {
    inputRef.current.select();
  };
  
  return (
    <div>
      <input
        ref={inputRef}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button onClick={handleFocus}>Focus</button>
      <button onClick={handleSelectAll}>Select All</button>
    </div>
  );
};

// Integration with third-party library
const ThirdPartyInput = () => {
  const containerRef = useRef(null);
  
  useEffect(() => {
    // Initialize third-party library
    const instance = ExternalLibrary.init(containerRef.current, {
      onChange: (value) => {
        console.log('Value changed:', value);
      }
    });
    
    return () => {
      instance.destroy();
    };
  }, []);
  
  return <div ref={containerRef} />;
};
Warning: Prefer controlled components for most use cases. Use uncontrolled only for file inputs, integration with non-React code, or when you need direct DOM manipulation.

6.3 Form Validation Patterns and Error Handling

Strategy When to Validate Implementation User Experience
On Submit Form submission Validate all fields in submit handler Less intrusive, batch errors
On Blur Field loses focus Validate in onBlur handler Immediate feedback after editing
On Change Every keystroke Validate in onChange handler Real-time feedback (can be annoying)
Debounced After pause in typing useDebounce + validation Balance between immediate and batch
Mixed Different rules per field Combine strategies Optimal UX per field type

Example: Form validation patterns

// Validation on submit
const SubmitValidation = () => {
  const [formData, setFormData] = useState({ email: '', password: '' });
  const [errors, setErrors] = 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 < 8) {
      newErrors.password = 'Password must be at least 8 characters';
    }
    
    return newErrors;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    const validationErrors = validate();
    
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }
    
    // Submit form
    console.log('Valid form data:', formData);
    setErrors({});
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          name="email"
          value={formData.email}
          onChange={handleChange}
          placeholder="Email"
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      <div>
        <input
          name="password"
          type="password"
          value={formData.password}
          onChange={handleChange}
          placeholder="Password"
        />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

// Validation on blur (after editing)
const BlurValidation = () => {
  const [email, setEmail] = useState('');
  const [touched, setTouched] = useState(false);
  const [error, setError] = useState('');
  
  const validateEmail = (value) => {
    if (!value) return 'Email is required';
    if (!/\S+@\S+\.\S+/.test(value)) return 'Email is invalid';
    return '';
  };
  
  const handleBlur = () => {
    setTouched(true);
    setError(validateEmail(email));
  };
  
  return (
    <div>
      <input
        value={email}
        onChange={(e) => {
          setEmail(e.target.value);
          if (touched) setError(validateEmail(e.target.value));
        }}
        onBlur={handleBlur}
        placeholder="Email"
      />
      {touched && error && <span className="error">{error}</span>}
    </div>
  );
};

// Custom validation hook
const useFormValidation = (initialState, validationRules) => {
  const [values, setValues] = useState(initialState);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const validate = (fieldName, value) => {
    const rule = validationRules[fieldName];
    if (!rule) return '';
    
    for (const validator of rule) {
      const error = validator(value, values);
      if (error) return error;
    }
    return '';
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    
    if (touched[name]) {
      const error = validate(name, value);
      setErrors(prev => ({ ...prev, [name]: error }));
    }
  };
  
  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));
    const error = validate(name, values[name]);
    setErrors(prev => ({ ...prev, [name]: error }));
  };
  
  const validateAll = () => {
    const newErrors = {};
    Object.keys(validationRules).forEach(field => {
      const error = validate(field, values[field]);
      if (error) newErrors[field] = error;
    });
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  return {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    validateAll
  };
};

// Usage
const ValidationForm = () => {
  const { values, errors, touched, handleChange, handleBlur, validateAll } = 
    useFormValidation(
      { email: '', password: '' },
      {
        email: [
          (v) => !v ? 'Required' : '',
          (v) => !/\S+@\S+\.\S+/.test(v) ? 'Invalid email' : ''
        ],
        password: [
          (v) => !v ? 'Required' : '',
          (v) => v.length < 8 ? 'Min 8 characters' : ''
        ]
      }
    );
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (validateAll()) {
      console.log('Valid:', values);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      {touched.email && errors.email && <span>{errors.email}</span>}
      <button type="submit">Submit</button>
    </form>
  );
};

6.4 Custom Input Components and Hooks

Pattern Purpose Benefits Example
Wrapper Components Consistent styling and behavior Reusability, DRY, branding Custom TextInput with label/error
useInput Hook Extract input state logic Cleaner components, reusable logic Hook returning value, onChange, reset
useForm Hook Manage entire form state Centralized form logic Hook with values, errors, handlers
Compound Components Flexible input structure Composable API, flexibility Input.Label, Input.Field, Input.Error
Controlled Wrappers Abstract third-party inputs Consistent API, testability Wrap date picker with standard props

Example: Custom input components and hooks

// Custom input hook
const useInput = (initialValue = '', validation) => {
  const [value, setValue] = useState(initialValue);
  const [error, setError] = useState('');
  const [touched, setTouched] = useState(false);
  
  const onChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    
    if (touched && validation) {
      setError(validation(newValue));
    }
  };
  
  const onBlur = () => {
    setTouched(true);
    if (validation) {
      setError(validation(value));
    }
  };
  
  const reset = () => {
    setValue(initialValue);
    setError('');
    setTouched(false);
  };
  
  return {
    value,
    error,
    touched,
    onChange,
    onBlur,
    reset,
    bind: { value, onChange, onBlur }
  };
};

// Usage
const LoginForm = () => {
  const email = useInput('', (v) => 
    !v ? 'Required' : !/\S+@\S+/.test(v) ? 'Invalid' : ''
  );
  const password = useInput('', (v) => 
    v.length < 8 ? 'Min 8 chars' : ''
  );
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (!email.error && !password.error) {
      console.log({ email: email.value, password: password.value });
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input {...email.bind} placeholder="Email" />
      {email.touched && email.error && <span>{email.error}</span>}
      
      <input {...password.bind} type="password" placeholder="Password" />
      {password.touched && password.error && <span>{password.error}</span>}
      
      <button type="submit">Login</button>
    </form>
  );
};

// Custom input wrapper component
const TextField = ({ label, error, touched, ...props }) => {
  return (
    <div className="field">
      {label && <label>{label}</label>}
      <input {...props} className={error && touched ? 'error' : ''} />
      {touched && error && (
        <span className="error-message">{error}</span>
      )}
    </div>
  );
};

// Usage
const FormWithTextField = () => {
  const email = useInput('');
  
  return (
    <TextField
      label="Email"
      type="email"
      error={email.error}
      touched={email.touched}
      {...email.bind}
    />
  );
};

// Compound component pattern
const Input = ({ children, error, touched }) => {
  return (
    <div className="input-group">
      {children}
      {touched && error && <Input.Error>{error}</Input.Error>}
    </div>
  );
};

Input.Label = ({ children, htmlFor }) => (
  <label htmlFor={htmlFor} className="input-label">{children}</label>
);

Input.Field = ({ id, ...props }) => (
  <input id={id} className="input-field" {...props} />
);

Input.Error = ({ children }) => (
  <span className="input-error">{children}</span>
);

// Usage
const CompoundForm = () => {
  const email = useInput('');
  
  return (
    <Input error={email.error} touched={email.touched}>
      <Input.Label htmlFor="email">Email</Input.Label>
      <Input.Field id="email" type="email" {...email.bind} />
    </Input>
  );
};

// Custom form hook
const useForm = (initialValues, validationSchema) => {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  const validateField = (name, value) => {
    if (validationSchema[name]) {
      return validationSchema[name](value, values);
    }
    return '';
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    
    if (touched[name]) {
      const error = validateField(name, value);
      setErrors(prev => ({ ...prev, [name]: error }));
    }
  };
  
  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));
    const error = validateField(name, values[name]);
    setErrors(prev => ({ ...prev, [name]: error }));
  };
  
  const handleSubmit = async (onSubmit) => {
    return async (e) => {
      e.preventDefault();
      
      // Validate all fields
      const newErrors = {};
      Object.keys(validationSchema).forEach(field => {
        const error = validateField(field, values[field]);
        if (error) newErrors[field] = error;
      });
      
      setErrors(newErrors);
      setTouched(Object.keys(validationSchema).reduce(
        (acc, key) => ({ ...acc, [key]: true }), {}
      ));
      
      if (Object.keys(newErrors).length === 0) {
        setIsSubmitting(true);
        try {
          await onSubmit(values);
        } finally {
          setIsSubmitting(false);
        }
      }
    };
  };
  
  const reset = () => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
  };
  
  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    reset
  };
};

6.5 Form Libraries Integration (Formik, React Hook Form)

Library Philosophy Pros Cons
Formik Component-based, render props Easy to learn, good DX, field-level validation More re-renders, larger bundle
React Hook Form Hook-based, uncontrolled inputs High performance, small bundle, less re-renders Steeper learning curve, ref-based
Final Form Framework-agnostic core Flexible, powerful, subscription-based More complex API
Yup / Zod Schema validation libraries Type-safe, reusable schemas, great DX Additional dependency

Example: Formik and React Hook Form

// FORMIK Example
import { useFormik } from 'formik';
import * as Yup from 'yup';

const FormikForm = () => {
  const formik = useFormik({
    initialValues: {
      email: '',
      password: ''
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .email('Invalid email')
        .required('Required'),
      password: Yup.string()
        .min(8, 'Must be 8 characters or more')
        .required('Required')
    }),
    onSubmit: async (values) => {
      await api.login(values);
    }
  });
  
  return (
    <form onSubmit={formik.handleSubmit}>
      <input
        name="email"
        type="email"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.email}
      />
      {formik.touched.email && formik.errors.email && (
        <div>{formik.errors.email}</div>
      )}
      
      <input
        name="password"
        type="password"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.password}
      />
      {formik.touched.password && formik.errors.password && (
        <div>{formik.errors.password}</div>
      )}
      
      <button type="submit" disabled={formik.isSubmitting}>
        Submit
      </button>
    </form>
  );
};

// Formik with Formik components
import { Formik, Form, Field, ErrorMessage } from 'formik';

const FormikComponents = () => (
  <Formik
    initialValues={{ email: '', password: '' }}
    validationSchema={validationSchema}
    onSubmit={async (values) => {
      await api.login(values);
    }}
  >
    {({ isSubmitting }) => (
      <Form>
        <Field name="email" type="email" />
        <ErrorMessage name="email" component="div" />
        
        <Field name="password" type="password" />
        <ErrorMessage name="password" component="div" />
        
        <button type="submit" disabled={isSubmitting}>
          Submit
        </button>
      </Form>
    )}
  </Formik>
);

// REACT HOOK FORM Example
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email').min(1, 'Required'),
  password: z.string().min(8, 'Must be at least 8 characters')
});

const ReactHookForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }
  } = useForm({
    resolver: zodResolver(schema)
  });
  
  const onSubmit = async (data) => {
    await api.login(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('email')}
        type="email"
        placeholder="Email"
      />
      {errors.email && <span>{errors.email.message}</span>}
      
      <input
        {...register('password')}
        type="password"
        placeholder="Password"
      />
      {errors.password && <span>{errors.password.message}</span>}
      
      <button type="submit" disabled={isSubmitting}>
        Submit
      </button>
    </form>
  );
};

// React Hook Form with Controller (for custom components)
import { Controller } from 'react-hook-form';

const RHFWithController = () => {
  const { control, handleSubmit } = useForm();
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="customInput"
        control={control}
        rules={{ required: 'This field is required' }}
        render={({ field, fieldState }) => (
          <div>
            <CustomInput {...field} />
            {fieldState.error && <span>{fieldState.error.message}</span>}
          </div>
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
};
Note: React Hook Form is generally recommended for performance-critical apps due to fewer re-renders. Formik offers easier learning curve and better integration with controlled components.

6.6 File Upload and FormData Handling

Feature Implementation Key Points Browser API
File Input <input type="file" ref={fileRef} /> Always uncontrolled Access via ref.current.files
Multiple Files <input type="file" multiple /> files is FileList (array-like) Array.from(files)
File Preview URL.createObjectURL(file) Create temporary URL Revoke with URL.revokeObjectURL
FormData new FormData() Build multipart/form-data Append files and fields
Drag and Drop onDrop, onDragOver handlers e.dataTransfer.files preventDefault() required
Progress XMLHttpRequest or axios onUploadProgress callback Track upload percentage

Example: File upload and FormData

// Basic file upload
const FileUpload = () => {
  const [file, setFile] = useState(null);
  const [preview, setPreview] = useState(null);
  
  const handleFileChange = (e) => {
    const selectedFile = e.target.files[0];
    if (selectedFile) {
      setFile(selectedFile);
      
      // Create preview URL for images
      if (selectedFile.type.startsWith('image/')) {
        const url = URL.createObjectURL(selectedFile);
        setPreview(url);
      }
    }
  };
  
  const handleUpload = async () => {
    if (!file) return;
    
    const formData = new FormData();
    formData.append('file', file);
    formData.append('description', 'My file');
    
    try {
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
      });
      const data = await response.json();
      console.log('Upload success:', data);
    } catch (error) {
      console.error('Upload failed:', error);
    }
  };
  
  // Cleanup preview URL
  useEffect(() => {
    return () => {
      if (preview) URL.revokeObjectURL(preview);
    };
  }, [preview]);
  
  return (
    <div>
      <input type="file" onChange={handleFileChange} accept="image/*" />
      {preview && <img src={preview} alt="Preview" style={{ width: 200 }} />}
      {file && (
        <div>
          <p>File: {file.name}</p>
          <p>Size: {(file.size / 1024).toFixed(2)} KB</p>
          <button onClick={handleUpload}>Upload</button>
        </div>
      )}
    </div>
  );
};

// Multiple file upload
const MultiFileUpload = () => {
  const [files, setFiles] = useState([]);
  
  const handleFileChange = (e) => {
    const fileList = Array.from(e.target.files);
    setFiles(fileList);
  };
  
  const handleUpload = async () => {
    const formData = new FormData();
    files.forEach((file, index) => {
      formData.append(\`files[\${index}]\`, file);
    });
    
    await fetch('/api/upload-multiple', {
      method: 'POST',
      body: formData
    });
  };
  
  return (
    <div>
      <input type="file" multiple onChange={handleFileChange} />
      <ul>
        {files.map((file, index) => (
          <li key={index}>{file.name} - {file.size} bytes</li>
        ))}
      </ul>
      {files.length > 0 && (
        <button onClick={handleUpload}>Upload All</button>
      )}
    </div>
  );
};

// Drag and drop file upload
const DragDropUpload = () => {
  const [files, setFiles] = useState([]);
  const [isDragging, setIsDragging] = useState(false);
  
  const handleDragOver = (e) => {
    e.preventDefault();
    setIsDragging(true);
  };
  
  const handleDragLeave = () => {
    setIsDragging(false);
  };
  
  const handleDrop = (e) => {
    e.preventDefault();
    setIsDragging(false);
    
    const droppedFiles = Array.from(e.dataTransfer.files);
    setFiles(prev => [...prev, ...droppedFiles]);
  };
  
  return (
    <div
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
      style={{
        border: isDragging ? '2px solid blue' : '2px dashed gray',
        padding: '2rem',
        textAlign: 'center'
      }}
    >
      {files.length === 0 ? (
        <p>Drag and drop files here</p>
      ) : (
        <ul>
          {files.map((file, i) => (
            <li key={i}>{file.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

// Upload with progress
const UploadWithProgress = () => {
  const [file, setFile] = useState(null);
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);
  
  const handleUpload = async () => {
    if (!file) return;
    
    setUploading(true);
    const formData = new FormData();
    formData.append('file', file);
    
    try {
      // Using axios for upload progress
      const response = await axios.post('/api/upload', formData, {
        onUploadProgress: (progressEvent) => {
          const percentCompleted = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          setProgress(percentCompleted);
        }
      });
      console.log('Upload complete:', response.data);
    } catch (error) {
      console.error('Upload failed:', error);
    } finally {
      setUploading(false);
      setProgress(0);
    }
  };
  
  return (
    <div>
      <input
        type="file"
        onChange={(e) => setFile(e.target.files[0])}
        disabled={uploading}
      />
      {uploading && (
        <div>
          <progress value={progress} max="100" />
          <span>{progress}%</span>
        </div>
      )}
      {file && !uploading && (
        <button onClick={handleUpload}>Upload</button>
      )}
    </div>
  );
};

// Complete form with file and other fields
const CompleteForm = () => {
  const [formData, setFormData] = useState({
    title: '',
    description: '',
    file: null
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };
  
  const handleFileChange = (e) => {
    setFormData(prev => ({ ...prev, file: e.target.files[0] }));
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const data = new FormData();
    data.append('title', formData.title);
    data.append('description', formData.description);
    if (formData.file) {
      data.append('file', formData.file);
    }
    
    await fetch('/api/submit', {
      method: 'POST',
      body: data
    });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="title"
        value={formData.title}
        onChange={handleChange}
        placeholder="Title"
      />
      <textarea
        name="description"
        value={formData.description}
        onChange={handleChange}
        placeholder="Description"
      />
      <input type="file" onChange={handleFileChange} />
      <button type="submit">Submit</button>
    </form>
  );
};
Warning: Always validate file types and sizes on the server. Client-side validation (accept attribute) is only for UX. Remember to revoke object URLs with URL.revokeObjectURL() to prevent memory leaks.

Forms Best Practices

  • Controlled components: Prefer for most inputs - React state as single source of truth
  • Validation timing: Validate on blur for better UX, on submit as final check
  • Form libraries: Use React Hook Form for performance, Formik for easier DX
  • Custom hooks: Extract form logic for reusability and cleaner components
  • File uploads: Always uncontrolled, use FormData, validate server-side
  • Error messages: Show after touched, clear on change, batch on submit

7. Event Handling and User Interactions

7.1 Event Handler Syntax and Event Objects

Syntax Pattern Description Use Case
Inline Arrow onClick={() => handler()} Arrow function in JSX Pass arguments, simple calls
Function Reference onClick={handler} Direct function reference Better performance, no args
Event Object (e) => handler(e) Access event properties Get target value, prevent default
With Parameters onClick={(e) => handler(id, e)} Pass custom args with event Item actions, list operations
Bind Method onClick={this.handler.bind(this, id)} Class component binding Legacy class components
Prevent Default e.preventDefault() Stop default browser action Forms, links, context menu
Stop Propagation e.stopPropagation() Stop event bubbling Nested clickable elements

Example: Event handler patterns

// Basic event handlers
const EventHandlers = () => {
  const [count, setCount] = useState(0);
  
  // Simple handler
  const handleClick = () => {
    setCount(c => c + 1);
  };
  
  // Handler with event object
  const handleChange = (e) => {
    console.log('Input value:', e.target.value);
  };
  
  // Handler with custom parameters
  const handleDelete = (id) => {
    console.log('Delete item:', id);
  };
  
  // Handler with both custom params and event
  const handleEdit = (id, e) => {
    e.stopPropagation(); // Stop bubbling
    console.log('Edit item:', id);
  };
  
  return (
    <div>
      {/* Function reference (preferred for simple cases) */}
      <button onClick={handleClick}>Count: {count}</button>
      
      {/* Inline arrow function (needed for params) */}
      <button onClick={() => handleDelete(123)}>Delete</button>
      
      {/* With event and params */}
      <button onClick={(e) => handleEdit(456, e)}>Edit</button>
      
      {/* Event object access */}
      <input onChange={handleChange} />
    </div>
  );
};

// Prevent default behavior
const FormWithLink = () => {
  const handleSubmit = (e) => {
    e.preventDefault(); // Don't reload page
    console.log('Form submitted');
  };
  
  const handleLinkClick = (e) => {
    e.preventDefault(); // Don't navigate
    console.log('Handle navigation in React');
  };
  
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" />
        <button type="submit">Submit</button>
      </form>
      
      <a href="/page" onClick={handleLinkClick}>
        Custom navigation
      </a>
    </div>
  );
};

// Stop propagation for nested elements
const NestedClickables = () => {
  const handleParentClick = () => {
    console.log('Parent clicked');
  };
  
  const handleChildClick = (e) => {
    e.stopPropagation(); // Don't trigger parent
    console.log('Child clicked');
  };
  
  return (
    <div onClick={handleParentClick} style={{ padding: '2rem', border: '1px solid' }}>
      Parent
      <button onClick={handleChildClick}>
        Child (won't trigger parent)
      </button>
    </div>
  );
};

// Event object properties
const EventProperties = () => {
  const handleEvent = (e) => {
    console.log('Event type:', e.type);
    console.log('Target element:', e.target);
    console.log('Current target:', e.currentTarget);
    console.log('Key pressed:', e.key);
    console.log('Mouse position:', e.clientX, e.clientY);
    console.log('Modifier keys:', {
      ctrl: e.ctrlKey,
      shift: e.shiftKey,
      alt: e.altKey,
      meta: e.metaKey
    });
  };
  
  return (
    <div>
      <button onClick={handleEvent}>Click me</button>
      <input onKeyDown={handleEvent} />
    </div>
  );
};
Note: Use function references when possible for better performance. Use inline arrow functions only when you need to pass arguments or access closure variables.

7.2 Synthetic Events and Cross-browser Compatibility

Feature Description Benefits Important Notes
Synthetic Events React's wrapper around native events Cross-browser consistency Same API across all browsers
Event Pooling (Legacy) Events reused for performance Memory efficiency Removed in React 17+
nativeEvent e.nativeEvent access Access underlying browser event Use only when necessary
Event Delegation Events attached to root (React 16) Performance, memory efficiency Changed in React 17 (root container)
Passive Events Non-blocking scroll/touch events Better scroll performance Can't preventDefault() on passive

Example: Synthetic events and cross-browser handling

// Synthetic event properties
const SyntheticEventDemo = () => {
  const handleClick = (e) => {
    // Synthetic event properties (same across browsers)
    console.log('Synthetic event:', e);
    console.log('Type:', e.type);
    console.log('Target:', e.target);
    console.log('Timestamp:', e.timeStamp);
    
    // Access native browser event if needed
    console.log('Native event:', e.nativeEvent);
    
    // Common methods work consistently
    e.preventDefault();
    e.stopPropagation();
  };
  
  return <button onClick={handleClick}>Click</button>;
};

// No need to worry about event pooling in React 17+
const ModernEventHandling = () => {
  const handleClick = (e) => {
    // In React 17+, can access event properties asynchronously
    setTimeout(() => {
      console.log('Event type:', e.type); // Works!
      console.log('Target:', e.target);   // Works!
    }, 1000);
  };
  
  return <button onClick={handleClick}>Click</button>;
};

// Legacy React (<17) required persist() for async access
const LegacyAsyncEventAccess = () => {
  const handleClick = (e) => {
    e.persist(); // No longer needed in React 17+
    
    setTimeout(() => {
      console.log('Event type:', e.type);
    }, 1000);
  };
  
  return <button onClick={handleClick}>Click</button>;
};

// Native event access for special cases
const NativeEventAccess = () => {
  const handleWheel = (e) => {
    // Synthetic event
    console.log('Delta Y:', e.deltaY);
    
    // Native event for browser-specific features
    const nativeEvent = e.nativeEvent;
    console.log('Native wheel event:', nativeEvent);
  };
  
  return (
    <div
      onWheel={handleWheel}
      style={{ height: '200px', overflow: 'auto' }}
    >
      Scroll me
    </div>
  );
};

// Cross-browser event handling
const CrossBrowserEvents = () => {
  const handleInput = (e) => {
    // Works consistently across all browsers
    const value = e.target.value;
    console.log('Input value:', value);
  };
  
  const handlePaste = (e) => {
    // Clipboard access works consistently
    const pastedText = e.clipboardData.getData('text');
    console.log('Pasted:', pastedText);
  };
  
  const handleDrag = (e) => {
    // Drag events normalized
    console.log('Dragging:', e.dataTransfer);
  };
  
  return (
    <div>
      <input onChange={handleInput} onPaste={handlePaste} />
      <div draggable onDrag={handleDrag}>Drag me</div>
    </div>
  );
};

// Event delegation (automatic in React)
const EventDelegationExample = () => {
  // React automatically delegates events to root
  // No need to manually attach/remove listeners
  
  const handleClick = (id) => {
    console.log('Clicked item:', id);
  };
  
  return (
    <ul>
      {[1, 2, 3, 4, 5].map(id => (
        <li key={id} onClick={() => handleClick(id)}>
          Item {id}
        </li>
      ))}
    </ul>
  );
  // React attaches ONE listener at root, not 5 listeners
};
Note: React's synthetic events provide consistent behavior across browsers. Event pooling was removed in React 17+, so you can safely access event properties asynchronously without calling persist().

7.3 Event Handler Binding and Performance

Pattern Performance When to Use Example
Inline Arrow Creates new function each render Simple cases, rare renders onClick={() => handler()}
useCallback Memoized function reference Optimize child re-renders const cb = useCallback(() => {}, [])
Function Reference Best - stable reference No arguments needed onClick={handler}
Closure Factory Creates function per item List items with IDs const makeHandler = (id) => () => {}
Data Attributes Single handler, read from event Lists with simple actions data-id={id} then read in handler

Example: Event handler optimization

// ❌ Bad: Inline arrow in child with memo
const Child = memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});

const BadParent = () => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      {/* Child re-renders every time because new function */}
      <Child onClick={() => console.log('clicked')} />
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>
  );
};

// ✓ Good: useCallback for stable reference
const GoodParent = () => {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return (
    <div>
      {/* Child won't re-render unnecessarily */}
      <Child onClick={handleClick} />
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>
  );
};

// ❌ Bad: Creating new function per list item
const BadList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const handleDelete = (id) => {
    setItems(items => items.filter(i => i !== id));
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item}>
          {item}
          {/* New function created for each item */}
          <button onClick={() => handleDelete(item)}>Delete</button>
        </li>
      ))}
    </ul>
  );
};

// ✓ Better: Data attributes pattern
const BetterList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const handleDelete = (e) => {
    const id = parseInt(e.currentTarget.dataset.id);
    setItems(items => items.filter(i => i !== id));
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item}>
          {item}
          {/* Single handler reference */}
          <button data-id={item} onClick={handleDelete}>Delete</button>
        </li>
      ))}
    </ul>
  );
};

// ✓ Good: Memoized list items
const ListItem = memo(({ item, onDelete }) => {
  return (
    <li>
      {item}
      <button onClick={onDelete}>Delete</button>
    </li>
  );
});

const OptimizedList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const handleDelete = useCallback((id) => {
    setItems(items => items.filter(i => i !== id));
  }, []);
  
  return (
    <ul>
      {items.map(item => (
        <ListItem
          key={item}
          item={item}
          onDelete={() => handleDelete(item)}
        />
      ))}
    </ul>
  );
};

// Performance optimization decision guide
const PerformanceGuide = () => {
  // Simple component, no memo on children → inline is fine
  const simple = () => (
    <button onClick={() => console.log('click')}>Click</button>
  );
  
  // Memo'ed children → use useCallback
  const [count, setCount] = useState(0);
  const optimized = useCallback(() => {
    console.log('click');
  }, []);
  
  // Large lists → use data attributes or memoized items
  // Rare updates → inline is fine
  // Frequent updates → optimize
};
Warning: Don't prematurely optimize! Inline arrow functions are fine for most cases. Only use useCallback when passing handlers to memoized children or in large lists with frequent updates.

7.4 Custom Event Handlers and Event Composition

Pattern Purpose Implementation Use Case
Event Composition Chain multiple handlers Call multiple functions in sequence Logging + action
Higher-Order Handlers Create handlers with common logic Function that returns handler Tracking, validation
Custom Events Component-specific events Custom event callbacks via props onSuccess, onError callbacks
Event Middlewares Transform or validate events Wrapper function around handler Auth checks, rate limiting
Debounced/Throttled Control event frequency useDebounce/useThrottle hooks Search input, scroll, resize

Example: Custom event handlers

// Event composition
const EventComposition = () => {
  const logEvent = (eventName) => {
    console.log(\`Event: \${eventName}\`);
  };
  
  const trackEvent = (eventName) => {
    analytics.track(eventName);
  };
  
  const handleClick = () => {
    console.log('Button clicked');
  };
  
  // Compose multiple handlers
  const composedHandler = (e) => {
    logEvent('button-click');
    trackEvent('button-click');
    handleClick();
  };
  
  return <button onClick={composedHandler}>Click</button>;
};

// Higher-order handler
const withTracking = (handler, eventName) => {
  return (e) => {
    analytics.track(eventName);
    handler(e);
  };
};

const withValidation = (handler, validate) => {
  return (e) => {
    if (validate(e)) {
      handler(e);
    }
  };
};

const TrackedButton = () => {
  const handleClick = () => {
    console.log('Clicked');
  };
  
  const trackedHandler = withTracking(handleClick, 'button-clicked');
  
  return <button onClick={trackedHandler}>Click</button>;
};

// Custom events via props
const FileUploader = ({ onUploadStart, onUploadSuccess, onUploadError }) => {
  const [uploading, setUploading] = useState(false);
  
  const handleUpload = async (file) => {
    setUploading(true);
    onUploadStart?.();
    
    try {
      const result = await uploadFile(file);
      onUploadSuccess?.(result);
    } catch (error) {
      onUploadError?.(error);
    } finally {
      setUploading(false);
    }
  };
  
  return (
    <input
      type="file"
      onChange={(e) => handleUpload(e.target.files[0])}
      disabled={uploading}
    />
  );
};

// Usage
const App = () => (
  <FileUploader
    onUploadStart={() => console.log('Starting...')}
    onUploadSuccess={(data) => console.log('Success:', data)}
    onUploadError={(err) => console.error('Error:', err)}
  />
);

// Debounced event handler
const useDebounce = (callback, delay) => {
  const timeoutRef = useRef(null);
  
  return useCallback((...args) => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    timeoutRef.current = setTimeout(() => {
      callback(...args);
    }, delay);
  }, [callback, delay]);
};

const SearchInput = () => {
  const [query, setQuery] = useState('');
  
  const performSearch = (searchTerm) => {
    console.log('Searching for:', searchTerm);
    // API call here
  };
  
  const debouncedSearch = useDebounce(performSearch, 500);
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };
  
  return <input value={query} onChange={handleChange} />;
};

// Throttled event handler
const useThrottle = (callback, delay) => {
  const lastRun = useRef(Date.now());
  
  return useCallback((...args) => {
    const now = Date.now();
    
    if (now - lastRun.current >= delay) {
      callback(...args);
      lastRun.current = now;
    }
  }, [callback, delay]);
};

const ScrollTracker = () => {
  const trackScroll = (scrollY) => {
    console.log('Scroll position:', scrollY);
  };
  
  const throttledTrack = useThrottle(() => {
    trackScroll(window.scrollY);
  }, 1000);
  
  useEffect(() => {
    window.addEventListener('scroll', throttledTrack);
    return () => window.removeEventListener('scroll', throttledTrack);
  }, [throttledTrack]);
  
  return <div>Scroll the page</div>;
};

// Event middleware pattern
const withAuth = (handler) => {
  return (e) => {
    const isAuthenticated = checkAuth();
    if (!isAuthenticated) {
      alert('Please log in');
      return;
    }
    handler(e);
  };
};

const ProtectedButton = () => {
  const handleAction = () => {
    console.log('Performing protected action');
  };
  
  return (
    <button onClick={withAuth(handleAction)}>
      Protected Action
    </button>
  );
};

7.5 Keyboard and Mouse Event Patterns

Event Type Event Name Common Properties Use Case
Keyboard onKeyDown, onKeyUp, onKeyPress key, code, keyCode, ctrlKey, shiftKey Shortcuts, form navigation, games
Mouse Click onClick, onDoubleClick, onContextMenu button, clientX, clientY Actions, menus, selection
Mouse Movement onMouseMove, onMouseEnter, onMouseLeave clientX, clientY, movementX, movementY Tooltips, drag, hover effects
Mouse Drag onMouseDown, onMouseMove, onMouseUp Track position changes Draggable elements, drawing
Wheel onWheel deltaX, deltaY, deltaZ Custom scrolling, zoom

Example: Keyboard and mouse events

// Keyboard shortcuts
const KeyboardShortcuts = () => {
  const handleKeyDown = (e) => {
    // Check for specific key
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
    
    // Keyboard shortcuts with modifiers
    if (e.ctrlKey && e.key === 's') {
      e.preventDefault();
      console.log('Save (Ctrl+S)');
    }
    
    if (e.ctrlKey && e.shiftKey && e.key === 'P') {
      e.preventDefault();
      console.log('Open command palette (Ctrl+Shift+P)');
    }
    
    // Arrow keys
    switch (e.key) {
      case 'ArrowUp':
        console.log('Up');
        break;
      case 'ArrowDown':
        console.log('Down');
        break;
      case 'ArrowLeft':
        console.log('Left');
        break;
      case 'ArrowRight':
        console.log('Right');
        break;
    }
  };
  
  return (
    <div onKeyDown={handleKeyDown} tabIndex={0}>
      Press keys (Ctrl+S, Arrows, Enter)
    </div>
  );
};

// Key codes reference
const KeyboardReference = () => {
  const handleKeyDown = (e) => {
    console.log('Key:', e.key);           // 'a', 'Enter', 'ArrowUp'
    console.log('Code:', e.code);         // 'KeyA', 'Enter', 'ArrowUp'
    console.log('KeyCode:', e.keyCode);   // 65, 13, 38 (deprecated)
    console.log('Modifiers:', {
      ctrl: e.ctrlKey,
      shift: e.shiftKey,
      alt: e.altKey,
      meta: e.metaKey  // Cmd on Mac, Win on Windows
    });
  };
  
  return <input onKeyDown={handleKeyDown} />;
};

// Mouse events
const MouseEvents = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isHovering, setIsHovering] = useState(false);
  
  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  const handleClick = (e) => {
    console.log('Click at:', e.clientX, e.clientY);
    console.log('Button:', e.button); // 0=left, 1=middle, 2=right
  };
  
  const handleDoubleClick = () => {
    console.log('Double clicked');
  };
  
  const handleContextMenu = (e) => {
    e.preventDefault(); // Prevent default context menu
    console.log('Right clicked');
  };
  
  return (
    <div
      onMouseMove={handleMouseMove}
      onClick={handleClick}
      onDoubleClick={handleDoubleClick}
      onContextMenu={handleContextMenu}
      onMouseEnter={() => setIsHovering(true)}
      onMouseLeave={() => setIsHovering(false)}
      style={{
        width: '100%',
        height: '300px',
        border: '1px solid',
        backgroundColor: isHovering ? '#f0f0f0' : 'white'
      }}
    >
      Mouse position: {position.x}, {position.y}
      {isHovering && ' (Hovering)'}
    </div>
  );
};

// Draggable element
const DraggableBox = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
  
  const handleMouseDown = (e) => {
    setIsDragging(true);
    setDragStart({
      x: e.clientX - position.x,
      y: e.clientY - position.y
    });
  };
  
  const handleMouseMove = (e) => {
    if (isDragging) {
      setPosition({
        x: e.clientX - dragStart.x,
        y: e.clientY - dragStart.y
      });
    }
  };
  
  const handleMouseUp = () => {
    setIsDragging(false);
  };
  
  useEffect(() => {
    if (isDragging) {
      window.addEventListener('mousemove', handleMouseMove);
      window.addEventListener('mouseup', handleMouseUp);
      
      return () => {
        window.removeEventListener('mousemove', handleMouseMove);
        window.removeEventListener('mouseup', handleMouseUp);
      };
    }
  }, [isDragging, dragStart]);
  
  return (
    <div
      onMouseDown={handleMouseDown}
      style={{
        position: 'absolute',
        left: position.x,
        top: position.y,
        width: '100px',
        height: '100px',
        backgroundColor: 'blue',
        cursor: isDragging ? 'grabbing' : 'grab'
      }}
    >
      Drag me
    </div>
  );
};

// Hover tooltip
const HoverTooltip = ({ children, tooltip }) => {
  const [show, setShow] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  const handleMouseEnter = (e) => {
    setShow(true);
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  return (
    <>
      <span
        onMouseEnter={handleMouseEnter}
        onMouseMove={handleMouseMove}
        onMouseLeave={() => setShow(false)}
      >
        {children}
      </span>
      {show && (
        <div
          style={{
            position: 'fixed',
            left: position.x + 10,
            top: position.y + 10,
            background: 'black',
            color: 'white',
            padding: '5px',
            borderRadius: '3px'
          }}
        >
          {tooltip}
        </div>
      )}
    </>
  );
};

7.6 Touch Events and Mobile Interaction Handling

Event Description Properties Use Case
onTouchStart Touch begins touches, targetTouches, changedTouches Detect touch, start gesture
onTouchMove Touch moves Touch coordinates, track movement Swipe, drag, pan
onTouchEnd Touch ends Remaining touches Complete gesture, trigger action
onTouchCancel Touch interrupted System interruption Reset gesture state
Multi-touch Multiple fingers touches array Pinch zoom, rotate
Gestures Swipe, pinch, rotate Calculate from touch positions Mobile interactions

Example: Touch events and mobile gestures

// Basic touch events
const TouchDemo = () => {
  const [touchInfo, setTouchInfo] = useState('');
  
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    setTouchInfo(\`Touch started at \${touch.clientX}, \${touch.clientY}\`);
  };
  
  const handleTouchMove = (e) => {
    const touch = e.touches[0];
    setTouchInfo(\`Moving at \${touch.clientX}, \${touch.clientY}\`);
  };
  
  const handleTouchEnd = () => {
    setTouchInfo('Touch ended');
  };
  
  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '100%',
        height: '200px',
        border: '1px solid',
        touchAction: 'none' // Prevent default touch behavior
      }}
    >
      {touchInfo || 'Touch here'}
    </div>
  );
};

// Swipe detection
const SwipeDetector = ({ onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown }) => {
  const [touchStart, setTouchStart] = useState(null);
  
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    setTouchStart({ x: touch.clientX, y: touch.clientY });
  };
  
  const handleTouchEnd = (e) => {
    if (!touchStart) return;
    
    const touch = e.changedTouches[0];
    const deltaX = touch.clientX - touchStart.x;
    const deltaY = touch.clientY - touchStart.y;
    
    const threshold = 50;
    
    // Determine swipe direction
    if (Math.abs(deltaX) > Math.abs(deltaY)) {
      // Horizontal swipe
      if (deltaX > threshold) {
        onSwipeRight?.();
      } else if (deltaX < -threshold) {
        onSwipeLeft?.();
      }
    } else {
      // Vertical swipe
      if (deltaY > threshold) {
        onSwipeDown?.();
      } else if (deltaY < -threshold) {
        onSwipeUp?.();
      }
    }
    
    setTouchStart(null);
  };
  
  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '100%',
        height: '300px',
        border: '1px solid',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
      }}
    >
      Swipe in any direction
    </div>
  );
};

// Usage
const SwipeDemo = () => (
  <SwipeDetector
    onSwipeLeft={() => console.log('Swiped left')}
    onSwipeRight={() => console.log('Swiped right')}
    onSwipeUp={() => console.log('Swiped up')}
    onSwipeDown={() => console.log('Swiped down')}
  />
);

// Pinch zoom
const PinchZoom = () => {
  const [scale, setScale] = useState(1);
  const [initialDistance, setInitialDistance] = useState(null);
  
  const getDistance = (touch1, touch2) => {
    const dx = touch1.clientX - touch2.clientX;
    const dy = touch1.clientY - touch2.clientY;
    return Math.sqrt(dx * dx + dy * dy);
  };
  
  const handleTouchStart = (e) => {
    if (e.touches.length === 2) {
      const distance = getDistance(e.touches[0], e.touches[1]);
      setInitialDistance(distance);
    }
  };
  
  const handleTouchMove = (e) => {
    if (e.touches.length === 2 && initialDistance) {
      e.preventDefault();
      
      const distance = getDistance(e.touches[0], e.touches[1]);
      const newScale = (distance / initialDistance) * scale;
      
      // Clamp scale between 0.5 and 3
      setScale(Math.min(Math.max(newScale, 0.5), 3));
    }
  };
  
  const handleTouchEnd = () => {
    setInitialDistance(null);
  };
  
  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '300px',
        height: '300px',
        border: '1px solid',
        overflow: 'hidden',
        touchAction: 'none'
      }}
    >
      <div
        style={{
          transform: \`scale(\${scale})\`,
          transformOrigin: 'center',
          transition: initialDistance ? 'none' : 'transform 0.3s'
        }}
      >
        Pinch to zoom (scale: {scale.toFixed(2)})
      </div>
    </div>
  );
};

// Draggable with touch support
const TouchDraggable = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const [startPos, setStartPos] = useState({ x: 0, y: 0 });
  
  const handleStart = (clientX, clientY) => {
    setIsDragging(true);
    setStartPos({
      x: clientX - position.x,
      y: clientY - position.y
    });
  };
  
  const handleMove = (clientX, clientY) => {
    if (isDragging) {
      setPosition({
        x: clientX - startPos.x,
        y: clientY - startPos.y
      });
    }
  };
  
  // Touch handlers
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    handleStart(touch.clientX, touch.clientY);
  };
  
  const handleTouchMove = (e) => {
    e.preventDefault();
    const touch = e.touches[0];
    handleMove(touch.clientX, touch.clientY);
  };
  
  // Mouse handlers (for desktop)
  const handleMouseDown = (e) => {
    handleStart(e.clientX, e.clientY);
  };
  
  const handleMouseMove = (e) => {
    handleMove(e.clientX, e.clientY);
  };
  
  const handleEnd = () => {
    setIsDragging(false);
  };
  
  useEffect(() => {
    if (isDragging) {
      window.addEventListener('mousemove', handleMouseMove);
      window.addEventListener('mouseup', handleEnd);
      
      return () => {
        window.removeEventListener('mousemove', handleMouseMove);
        window.removeEventListener('mouseup', handleEnd);
      };
    }
  }, [isDragging]);
  
  return (
    <div
      onMouseDown={handleMouseDown}
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleEnd}
      style={{
        position: 'absolute',
        left: position.x,
        top: position.y,
        width: '100px',
        height: '100px',
        backgroundColor: 'green',
        touchAction: 'none'
      }}
    >
      Drag me
    </div>
  );
};
Warning: Always set touchAction: 'none' CSS property when handling touch events to prevent default browser behaviors. Use e.preventDefault() carefully as it can interfere with scrolling.

Event Handling Best Practices

  • Function references: Use direct references when possible for better performance
  • useCallback: Memoize handlers passed to memo'ed children or large lists
  • Event delegation: React handles automatically - don't manually manage listeners
  • preventDefault/stopPropagation: Use judiciously to control event flow
  • Touch events: Support both mouse and touch for best mobile/desktop experience
  • Debounce/throttle: Control frequency for expensive operations (search, scroll)

8. Lists, Keys, and Dynamic Rendering

8.1 Array Rendering and map() Patterns

Pattern Syntax Description Use Case
Basic map() array.map(item => JSX) Transform array to JSX elements Simple list rendering
Index parameter array.map((item, i) => JSX) Access index in callback Numbered lists, positioning
Array parameter array.map((item, i, arr) => JSX) Access full array Context-aware rendering
Inline return {items.map(i => <div />)} Direct JSX return Concise single elements
Block return {items.map(i => { return <div /> })} Explicit return statement Multiple operations before return
Fragment wrapping map(i => <>...</>) Multiple elements without wrapper Flat DOM structure
Destructuring map(({id, name}) => JSX) Extract properties directly Cleaner variable access

Example: Basic list rendering patterns

// Simple list rendering
const UserList = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

// With index for numbering
const NumberedList = ({ items }) => (
  <ol>
    {items.map((item, index) => (
      <li key={item.id}>
        #{index + 1}: {item.text}
      </li>
    ))}
  </ol>
);

// Destructuring in map
const ProductList = ({ products }) => (
  <div>
    {products.map(({ id, name, price, stock }) => (
      <div key={id} className="product">
        <h3>{name}</h3>
        <p>${price.toFixed(2)}</p>
        {stock < 10 && <span>Low stock!</span>}
      </div>
    ))}
  </div>
);

// Multiple elements with fragment
const Timeline = ({ events }) => (
  <div>
    {events.map(event => (
      <React.Fragment key={event.id}>
        <h3>{event.title}</h3>
        <p>{event.description}</p>
        <time>{event.date}</time>
        <hr />
      </React.Fragment>
    ))}
  </div>
);

// Complex logic with block
const PostList = ({ posts }) => (
  <div>
    {posts.map((post) => {
      const isNew = Date.now() - post.timestamp < 86400000;
      const authorName = post.author.name || 'Anonymous';
      
      return (
        <article key={post.id} className={isNew ? 'new' : ''}>
          <h2>{post.title}</h2>
          <p>By {authorName}</p>
          <p>{post.content}</p>
        </article>
      );
    })}
  </div>
);

// Nested arrays
const Categories = ({ categories }) => (
  <div>
    {categories.map(category => (
      <div key={category.id}>
        <h2>{category.name}</h2>
        <ul>
          {category.items.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      </div>
    ))}
  </div>
);
Note: Always return JSX from map() - common mistake is forgetting the return statement in block syntax. Use {} for expressions, not statements.

8.2 Key Prop Best Practices and Performance

Key Type Example Stability When to Use
Unique ID BEST key={item.id} Stable across renders Items from database/API
Compound key key={\`\${cat}-\${id}\`} Stable if parts stable Nested lists, multiple sources
Content hash key={hash(item)} Stable for same content Static data, computed keys
Index AVOID key={index} Unstable on reorder ONLY static immutable lists
Random/UUID NEVER key={Math.random()} Never stable Never - causes re-mount
Key Rule Description Impact if Violated
Unique among siblings Keys must be unique within array Incorrect element updates, bugs
Stable across renders Same item = same key Unnecessary re-mounts, state loss
Not globally unique Only unique in local array No issue - scoped to parent
No array index for dynamic Avoid index if items move/delete Wrong items update, state mismatch
Don't use objects Keys must be string/number Warning, uses toString()

Example: Key prop patterns and anti-patterns

// ✅ GOOD: Stable unique IDs from data
const GoodList = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
);

// ✅ GOOD: Compound keys for nested structures
const NestedGood = ({ categories }) => (
  <div>
    {categories.map(cat => (
      <div key={cat.id}>
        <h2>{cat.name}</h2>
        {cat.products.map(prod => (
          <div key={\`\${cat.id}-\${prod.id}\`}>
            {prod.name}
          </div>
        ))}
      </div>
    ))}
  </div>
);

// ⚠️ ACCEPTABLE: Index only for static lists
const StaticList = () => {
  const staticItems = ['Home', 'About', 'Contact']; // Never changes
  
  return (
    <ul>
      {staticItems.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
};

// ❌ BAD: Index with dynamic operations
const BadDynamic = ({ items, onDelete }) => (
  <ul>
    {items.map((item, index) => (
      <li key={index}> {/* BUG: Keys shift on delete */}
        <input defaultValue={item.name} />
        <button onClick={() => onDelete(index)}>Delete</button>
      </li>
    ))}
  </ul>
);

// ❌ BAD: Random/changing keys
const BadRandom = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={Math.random()}> {/* Always re-mounts */}
        {item.name}
      </li>
    ))}
  </ul>
);

// ❌ BAD: Non-unique keys
const BadDuplicate = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.type}> {/* Multiple items same type */}
        {item.name}
      </li>
    ))}
  </ul>
);

// ✅ GOOD: Generate stable ID if missing
const WithGeneratedID = ({ items }) => {
  const itemsWithIds = useMemo(() => 
    items.map((item, index) => ({
      ...item,
      _key: item.id || \`generated-\${index}-\${item.name}\`
    })),
    [items]
  );
  
  return (
    <ul>
      {itemsWithIds.map(item => (
        <li key={item._key}>{item.name}</li>
      ))}
    </ul>
  );
};
Warning: Using array index as key with reorderable/deletable lists causes bugs: component state (like input values) gets assigned to wrong items after operations.

8.3 Dynamic List Updates and State Management

Operation Pattern Immutability
Add to end [...items, newItem] Creates new array
Add to start [newItem, ...items] Creates new array
Insert at index [...items.slice(0, i), item, ...items.slice(i)] New array with insertion
Remove by index items.filter((_, i) => i !== index) New array without item
Remove by ID items.filter(i => i.id !== id) New array without item
Update by index items.map((i, idx) => idx === index ? {...i, prop} : i) New array with updated item
Update by ID items.map(i => i.id === id ? {...i, prop} : i) New array with updated item
Replace all setItems(newItems) Complete replacement
Sort [...items].sort(compareFn) Copy then sort
Reverse [...items].reverse() Copy then reverse

Example: Dynamic list operations with immutable state

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  
  // Add new todo
  const addTodo = () => {
    if (input.trim()) {
      setTodos([...todos, {
        id: Date.now(),
        text: input,
        completed: false
      }]);
      setInput('');
    }
  };
  
  // Delete todo by ID
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  // Toggle completed status
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };
  
  // Update todo text
  const updateTodo = (id, newText) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, text: newText }
        : todo
    ));
  };
  
  // Move todo up
  const moveUp = (index) => {
    if (index > 0) {
      const newTodos = [...todos];
      [newTodos[index - 1], newTodos[index]] = 
        [newTodos[index], newTodos[index - 1]];
      setTodos(newTodos);
    }
  };
  
  // Clear completed
  const clearCompleted = () => {
    setTodos(todos.filter(todo => !todo.completed));
  };
  
  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        onKeyPress={e => e.key === 'Enter' && addTodo()}
      />
      <button onClick={addTodo}>Add</button>
      
      <ul>
        {todos.map((todo, index) => (
          <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={() => moveUp(index)}>↑</button>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
      
      <button onClick={clearCompleted}>Clear Completed</button>
    </div>
  );
};

// Advanced: Batch updates with useReducer
const listReducer = (state, action) => {
  switch (action.type) {
    case 'ADD':
      return [...state, action.item];
    
    case 'DELETE':
      return state.filter(item => item.id !== action.id);
    
    case 'UPDATE':
      return state.map(item =>
        item.id === action.id
          ? { ...item, ...action.updates }
          : item
      );
    
    case 'REORDER':
      const result = [...state];
      const [removed] = result.splice(action.from, 1);
      result.splice(action.to, 0, removed);
      return result;
    
    case 'BULK_UPDATE':
      return state.map(item =>
        action.ids.includes(item.id)
          ? { ...item, ...action.updates }
          : item
      );
    
    default:
      return state;
  }
};

const AdvancedList = () => {
  const [items, dispatch] = useReducer(listReducer, []);
  
  return (
    <div>
      <button onClick={() => dispatch({
        type: 'ADD',
        item: { id: Date.now(), text: 'New item' }
      })}>
        Add Item
      </button>
      
      {items.map(item => (
        <div key={item.id}>
          {item.text}
          <button onClick={() => dispatch({
            type: 'DELETE',
            id: item.id
          })}>
            Delete
          </button>
        </div>
      ))}
    </div>
  );
};
Note: Never mutate state arrays directly (items.push(), items[0] = x). Always create new arrays to trigger React re-renders properly.

8.4 Conditional Rendering and Null Patterns

Pattern Syntax Renders Use Case
Logical AND {condition && <Component />} Component or nothing Simple show/hide
Ternary {condition ? <A /> : <B />} Either A or B Two alternatives
Null coalescing {value ?? 'default'} Value or default Null/undefined fallback
Optional chaining {obj?.prop?.value} Value or undefined Safe nested access
Array filter {arr.filter(cond).map(...)} Filtered items Conditional lists
Early return if (!data) return null; Nothing or rest Loading/error states
Empty fragment <>{condition && ...}</> Conditional children Multiple conditional elements
Nullish rendering {value || 'N/A'} Value or fallback Display defaults

Example: Conditional rendering patterns

// Logical AND for simple conditions
const Greeting = ({ isLoggedIn, username }) => (
  <div>
    <h1>Welcome</h1>
    {isLoggedIn && <p>Hello, {username}!</p>}
  </div>
);

// Ternary for either/or
const Status = ({ isOnline }) => (
  <div>
    {isOnline ? (
      <span className="online">Online</span>
    ) : (
      <span className="offline">Offline</span>
    )}
  </div>
);

// Early returns for complex conditions
const UserProfile = ({ user, isLoading, error }) => {
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>No user found</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
};

// Nested conditionals with optional chaining
const Address = ({ user }) => (
  <div>
    <p>{user?.address?.street ?? 'No address provided'}</p>
    <p>{user?.address?.city}, {user?.address?.country}</p>
  </div>
);

// Multiple conditions in list
const ItemList = ({ items, showOnlyActive, sortByName }) => {
  const filteredItems = items
    .filter(item => !showOnlyActive || item.active)
    .sort((a, b) => sortByName ? a.name.localeCompare(b.name) : 0);
  
  return (
    <ul>
      {filteredItems.length === 0 ? (
        <li>No items to display</li>
      ) : (
        filteredItems.map(item => (
          <li key={item.id}>
            {item.name}
            {item.isNew && <span className="badge">NEW</span>}
          </li>
        ))
      )}
    </ul>
  );
};

// Complex multi-condition rendering
const Dashboard = ({ user, permissions, settings }) => (
  <div>
    {/* Admin section */}
    {user.role === 'admin' && (
      <section>
        <h2>Admin Panel</h2>
        {permissions.canEdit && <button>Edit</button>}
        {permissions.canDelete && <button>Delete</button>}
      </section>
    )}
    
    {/* Premium features */}
    {user.isPremium ? (
      <PremiumFeatures />
    ) : (
      <div>
        <p>Upgrade to Premium</p>
        <button>Upgrade Now</button>
      </div>
    )}
    
    {/* Settings based on flags */}
    {settings.showNotifications && <NotificationPanel />}
    {settings.enableDarkMode && <ThemeToggle />}
  </div>
);

// Avoiding falsy value bugs
const Counter = ({ count }) => (
  <div>
    {/* ❌ BAD: renders "0" when count is 0 */}
    {count && <p>Count: {count}</p>}
    
    {/* ✅ GOOD: explicit check */}
    {count > 0 && <p>Count: {count}</p>}
    
    {/* ✅ GOOD: ternary for all cases */}
    {count ? <p>Count: {count}</p> : <p>No items</p>}
  </div>
);
Warning: Watch out for falsy values in logical AND: 0 && <Component /> renders "0", not nothing. Use explicit boolean checks or ternary operators.

8.5 List Filtering and Search Implementation

Technique Method Performance Use Case
Simple filter items.filter(predicate) O(n) Small lists, simple conditions
Case-insensitive search toLowerCase().includes() O(n×m) Text search
Multiple criteria filter(i => cond1 && cond2) O(n) Advanced filtering
Debounced search Delay filter execution Reduced calls Live search, API calls
Memoized filter useMemo(() => filter) Cached result Expensive filters, large lists
Index search Pre-built lookup maps O(1) lookup Very large datasets

Example: Search and filter implementations

// Basic search filter
const SearchList = () => {
  const [items] = useState([
    { id: 1, name: 'Apple', category: 'Fruit' },
    { id: 2, name: 'Banana', category: 'Fruit' },
    { id: 3, name: 'Carrot', category: 'Vegetable' }
  ]);
  const [search, setSearch] = useState('');
  
  const filteredItems = items.filter(item =>
    item.name.toLowerCase().includes(search.toLowerCase())
  );
  
  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

// Multi-criteria filter with memoization
const AdvancedFilter = () => {
  const [products, setProducts] = useState([]);
  const [filters, setFilters] = useState({
    search: '',
    category: 'all',
    minPrice: 0,
    maxPrice: 1000,
    inStock: false
  });
  
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      // Search filter
      if (filters.search && 
          !product.name.toLowerCase().includes(filters.search.toLowerCase())) {
        return false;
      }
      
      // Category filter
      if (filters.category !== 'all' && product.category !== filters.category) {
        return false;
      }
      
      // Price range
      if (product.price < filters.minPrice || product.price > filters.maxPrice) {
        return false;
      }
      
      // Stock filter
      if (filters.inStock && product.stock === 0) {
        return false;
      }
      
      return true;
    });
  }, [products, filters]);
  
  return (
    <div>
      <input
        value={filters.search}
        onChange={e => setFilters({...filters, search: e.target.value})}
        placeholder="Search products..."
      />
      
      <select
        value={filters.category}
        onChange={e => setFilters({...filters, category: e.target.value})}
      >
        <option value="all">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>
      
      <label>
        <input
          type="checkbox"
          checked={filters.inStock}
          onChange={e => setFilters({...filters, inStock: e.target.checked})}
        />
        In Stock Only
      </label>
      
      <p>Found {filteredProducts.length} products</p>
      
      <div>
        {filteredProducts.map(product => (
          <div key={product.id}>
            <h3>{product.name}</h3>
            <p>${product.price}</p>
            <p>Stock: {product.stock}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

// Debounced search for better performance
const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
};

const DebouncedSearch = () => {
  const [items] = useState([/* large array */]);
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 300);
  
  const filteredItems = useMemo(() => {
    if (!debouncedSearch) return items;
    
    return items.filter(item =>
      item.name.toLowerCase().includes(debouncedSearch.toLowerCase())
    );
  }, [items, debouncedSearch]);
  
  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search (debounced)..."
      />
      <p>Searching for: {debouncedSearch}</p>
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

// Sorting combined with filtering
const SortableFilterable = () => {
  const [items, setItems] = useState([]);
  const [search, setSearch] = useState('');
  const [sortBy, setSortBy] = useState('name');
  const [sortOrder, setSortOrder] = useState('asc');
  
  const processedItems = useMemo(() => {
    // First filter
    let result = items.filter(item =>
      item.name.toLowerCase().includes(search.toLowerCase())
    );
    
    // Then sort
    result.sort((a, b) => {
      const aVal = a[sortBy];
      const bVal = b[sortBy];
      
      if (typeof aVal === 'string') {
        return sortOrder === 'asc'
          ? aVal.localeCompare(bVal)
          : bVal.localeCompare(aVal);
      }
      
      return sortOrder === 'asc'
        ? aVal - bVal
        : bVal - aVal;
    });
    
    return result;
  }, [items, search, sortBy, sortOrder]);
  
  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
      />
      
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">Name</option>
        <option value="price">Price</option>
        <option value="date">Date</option>
      </select>
      
      <button onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}>
        {sortOrder === 'asc' ? '↑' : '↓'}
      </button>
      
      <ul>
        {processedItems.map(item => (
          <li key={item.id}>{item.name} - ${item.price}</li>
        ))}
      </ul>
    </div>
  );
};
Note: Use useMemo for expensive filter/sort operations on large lists. Add debouncing for search inputs to reduce unnecessary re-renders.

8.6 Virtualized Lists and Large Dataset Handling

Technique Description Best For Library
Windowing Render only visible items Long uniform lists react-window
Virtual scrolling Dynamic item height handling Variable height items react-virtualized
Infinite scroll Load more on scroll bottom Paginated data react-infinite-scroll
Pagination Page-based navigation Discrete data chunks Custom implementation
Lazy rendering Render on intersection Complex card layouts IntersectionObserver
Cursor-based Load by cursor position Real-time feeds API + state management

Example: Virtualized list with react-window

import { FixedSizeList } from 'react-window';

// Basic virtualized list
const VirtualList = ({ items }) => {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
};

// Variable height list
import { VariableSizeList } from 'react-window';

const VariableHeightList = ({ items }) => {
  const listRef = useRef();
  
  const getItemSize = (index) => {
    // Calculate height based on content
    return items[index].description.length > 100 ? 120 : 60;
  };
  
  const Row = ({ index, style }) => (
    <div style={style}>
      <h4>{items[index].title}</h4>
      <p>{items[index].description}</p>
    </div>
  );
  
  return (
    <VariableSizeList
      ref={listRef}
      height={600}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </VariableSizeList>
  );
};

// Infinite scroll implementation
const InfiniteScrollList = () => {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const observerRef = useRef();
  
  const lastItemRef = useCallback((node) => {
    if (loading) return;
    
    if (observerRef.current) observerRef.current.disconnect();
    
    observerRef.current = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && hasMore) {
        setPage(prev => prev + 1);
      }
    });
    
    if (node) observerRef.current.observe(node);
  }, [loading, hasMore]);
  
  useEffect(() => {
    const fetchMore = async () => {
      setLoading(true);
      const response = await fetch(\`/api/items?page=\${page}\`);
      const newItems = await response.json();
      
      setItems(prev => [...prev, ...newItems]);
      setHasMore(newItems.length > 0);
      setLoading(false);
    };
    
    fetchMore();
  }, [page]);
  
  return (
    <div>
      {items.map((item, index) => {
        if (items.length === index + 1) {
          return (
            <div ref={lastItemRef} key={item.id}>
              {item.name}
            </div>
          );
        }
        return <div key={item.id}>{item.name}</div>;
      })}
      {loading && <div>Loading...</div>}
    </div>
  );
};

// Pagination implementation
const PaginatedList = () => {
  const [items, setItems] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);
  const itemsPerPage = 20;
  
  useEffect(() => {
    const fetchPage = async () => {
      const response = await fetch(
        \`/api/items?page=\${currentPage}&limit=\${itemsPerPage}\`
      );
      const data = await response.json();
      
      setItems(data.items);
      setTotalPages(Math.ceil(data.total / itemsPerPage));
    };
    
    fetchPage();
  }, [currentPage]);
  
  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      
      <div className="pagination">
        <button
          onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
          disabled={currentPage === 1}
        >
          Previous
        </button>
        
        <span>Page {currentPage} of {totalPages}</span>
        
        <button
          onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
          disabled={currentPage === totalPages}
        >
          Next
        </button>
      </div>
    </div>
  );
};

// Lazy loading with intersection observer
const LazyList = ({ items }) => {
  const [visibleItems, setVisibleItems] = useState(new Set());
  
  const observeItem = useCallback((node, id) => {
    if (!node) return;
    
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            setVisibleItems(prev => new Set([...prev, id]));
            observer.disconnect();
          }
        });
      },
      { rootMargin: '100px' }
    );
    
    observer.observe(node);
  }, []);
  
  return (
    <div>
      {items.map(item => (
        <div
          key={item.id}
          ref={node => observeItem(node, item.id)}
          style={{ minHeight: '100px' }}
        >
          {visibleItems.has(item.id) ? (
            <ExpensiveComponent item={item} />
          ) : (
            <div>Loading...</div>
          )}
        </div>
      ))}
    </div>
  );
};
Performance Tips:
  • Use virtualization for lists > 100 items
  • Implement infinite scroll for continuous feeds
  • Prefer pagination for searchable/filterable data
  • Use IntersectionObserver for lazy loading images/components
  • Memoize row components with React.memo

Lists and Rendering Best Practices

9. Component Lifecycle and Effects

9.1 useEffect Hook Patterns and Dependencies

Pattern Dependencies Runs When Use Case
No dependencies useEffect(() => {}) Every render Rarely needed, usually a mistake
Empty array useEffect(() => {}, []) Once on mount Initial setup, subscriptions
With dependencies useEffect(() => {}, [a, b]) When a or b changes Sync with specific values
Single dependency useEffect(() => {}, [id]) When id changes Fetch data for specific ID
Object dependency useEffect(() => {}, [obj]) When object reference changes Can cause extra renders
Function dependency useEffect(() => {}, [fn]) When function reference changes Needs useCallback wrapper
Dependency Rule Description ESLint Rule
Include all used values List all props, state, vars used in effect exhaustive-deps
Primitive values Numbers, strings, booleans - safe No warning
Object/array deps New reference = triggers effect May need useMemo
Function deps Wrap with useCallback May need useCallback
Refs don't trigger ref.current changes ignored Safe to omit
setState stable setState functions don't change Safe to omit

Example: useEffect dependency patterns

// Run once on mount (componentDidMount equivalent)
useEffect(() => {
  console.log('Component mounted');
  document.title = 'My App';
}, []); // Empty dependency array

// Run on every render (usually wrong!)
useEffect(() => {
  console.log('Every render'); // Performance issue!
}); // No dependency array - avoid this

// Run when specific values change
useEffect(() => {
  console.log(\`Count changed to \${count}\`);
}, [count]); // Re-runs when count changes

// Multiple dependencies
useEffect(() => {
  console.log(\`User \${userId} on page \${page}\`);
  fetchUserData(userId, page);
}, [userId, page]); // Re-runs when either changes

// Derived values in effect
const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchUser = async () => {
      const data = await fetch(\`/api/users/\${userId}\`).then(r => r.json());
      if (!cancelled) {
        setUser(data);
      }
    };
    
    fetchUser();
    
    return () => {
      cancelled = true;
    };
  }, [userId]); // Only userId dependency needed
  
  return user ? <div>{user.name}</div> : <div>Loading...</div>;
};

// Object dependency issue
const BadObjectDep = ({ config }) => {
  useEffect(() => {
    // This runs every render if config is recreated!
    console.log(config.apiUrl);
  }, [config]); // ❌ Object reference changes each render
  
  return null;
};

// Fix: Extract primitive values
const GoodObjectDep = ({ config }) => {
  useEffect(() => {
    console.log(config.apiUrl);
  }, [config.apiUrl]); // ✅ Only re-run if apiUrl changes
  
  return null;
};

// Function dependency issue
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // ❌ New function every render
  const handleClick = () => {
    console.log(count);
  };
  
  return <Child onClick={handleClick} />;
};

const Child = ({ onClick }) => {
  useEffect(() => {
    // Runs every render because onClick changes
    console.log('onClick changed');
  }, [onClick]); // ❌ Function reference changes
  
  return <button onClick={onClick}>Click</button>;
};

// Fix: Use useCallback
const ParentFixed = () => {
  const [count, setCount] = useState(0);
  
  // ✅ Stable function reference
  const handleClick = useCallback(() => {
    console.log(count);
  }, [count]);
  
  return <ChildFixed onClick={handleClick} />;
};

// setState doesn't need to be in dependencies
const Counter = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1); // ✅ Functional update
    }, 1000);
    
    return () => clearInterval(timer);
  }, []); // setCount not needed in deps
  
  return <div>{count}</div>;
};
Warning: React's ESLint plugin (eslint-plugin-react-hooks) will warn about missing dependencies. Don't ignore these warnings - they prevent bugs. If you intentionally want to omit a dependency, use // eslint-disable-next-line react-hooks/exhaustive-deps with a comment explaining why.

9.2 Effect Cleanup and Memory Leak Prevention

Cleanup Scenario Without Cleanup With Cleanup Memory Leak Risk
Timers Continues running after unmount clearTimeout/Interval High
Event listeners Accumulate on every mount removeEventListener High
Subscriptions Connection stays open unsubscribe() High
Async operations setState on unmounted component Cancellation flag/AbortController Medium
WebSocket Connection remains open socket.close() High
Animation frames Continues running cancelAnimationFrame Medium
Observers Observers keep watching observer.disconnect() High

Example: Effect cleanup patterns

// Timer cleanup
const Timer = () => {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    // Cleanup function
    return () => {
      clearInterval(interval);
      console.log('Timer cleaned up');
    };
  }, []);
  
  return <div>Seconds: {seconds}</div>;
};

// Event listener cleanup
const WindowSize = () => {
  const [size, setSize] = useState({ width: 0, height: 0 });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    // Add listener
    window.addEventListener('resize', handleResize);
    handleResize(); // Initial call
    
    // Cleanup: remove listener
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  return <div>{size.width} x {size.height}</div>;
};

// Async operation cleanup (prevent setState on unmounted)
const UserData = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    let cancelled = false; // Cancellation flag
    
    const fetchUser = async () => {
      setLoading(true);
      
      try {
        const response = await fetch(\`/api/users/\${userId}\`);
        const data = await response.json();
        
        // Only update state if not cancelled
        if (!cancelled) {
          setUser(data);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          console.error(error);
          setLoading(false);
        }
      }
    };
    
    fetchUser();
    
    // Cleanup: set flag to prevent setState
    return () => {
      cancelled = true;
    };
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  return <div>{user?.name}</div>;
};

// AbortController for fetch cleanup (modern approach)
const DataFetcher = ({ endpoint }) => {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchData = async () => {
      try {
        const response = await fetch(endpoint, {
          signal: controller.signal
        });
        const result = await response.json();
        setData(result);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error(error);
        }
      }
    };
    
    fetchData();
    
    // Cleanup: abort fetch
    return () => {
      controller.abort();
    };
  }, [endpoint]);
  
  return <div>{JSON.stringify(data)}</div>;
};

// WebSocket cleanup
const LiveChat = ({ roomId }) => {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    const ws = new WebSocket(\`wss://chat.example.com/\${roomId}\`);
    
    ws.onopen = () => {
      console.log('Connected to chat');
    };
    
    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      setMessages(prev => [...prev, message]);
    };
    
    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
    
    // Cleanup: close WebSocket
    return () => {
      ws.close();
      console.log('Disconnected from chat');
    };
  }, [roomId]);
  
  return (
    <div>
      {messages.map((msg, i) => (
        <div key={i}>{msg.text}</div>
      ))}
    </div>
  );
};

// IntersectionObserver cleanup
const LazyImage = ({ src, alt }) => {
  const [isVisible, setIsVisible] = useState(false);
  const imgRef = useRef(null);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect(); // Stop observing once visible
        }
      },
      { threshold: 0.1 }
    );
    
    if (imgRef.current) {
      observer.observe(imgRef.current);
    }
    
    // Cleanup: disconnect observer
    return () => {
      observer.disconnect();
    };
  }, []);
  
  return (
    <div ref={imgRef}>
      {isVisible ? (
        <img src={src} alt={alt} />
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
};

// Animation frame cleanup
const AnimatedCounter = ({ target }) => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    let frame;
    let startTime = null;
    
    const animate = (timestamp) => {
      if (!startTime) startTime = timestamp;
      const progress = timestamp - startTime;
      
      const nextCount = Math.min(
        Math.floor((progress / 2000) * target),
        target
      );
      
      setCount(nextCount);
      
      if (nextCount < target) {
        frame = requestAnimationFrame(animate);
      }
    };
    
    frame = requestAnimationFrame(animate);
    
    // Cleanup: cancel animation
    return () => {
      cancelAnimationFrame(frame);
    };
  }, [target]);
  
  return <div>{count}</div>;
};
Warning: Always clean up side effects! Common memory leaks: forgetting to clear timers, not removing event listeners, leaving WebSockets open, or calling setState on unmounted components.

9.3 Effect Timing and useLayoutEffect

Hook Timing Blocks Paint Use Case
useEffect After paint (async) No Most side effects, data fetching
useLayoutEffect Before paint (sync) Yes DOM measurements, prevent flicker
Execution Order Phase Description
1. Render Component function runs JSX returned, state/props evaluated
2. React updates DOM DOM mutations Changes applied to DOM
3. useLayoutEffect Before browser paint Synchronous, blocks painting
4. Browser paints Visual update User sees changes
5. useEffect After paint Async, doesn't block paint

Example: useEffect vs useLayoutEffect timing

// useEffect - runs AFTER paint (preferred)
const DataComponent = ({ id }) => {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // Runs after component is painted
    // Good for data fetching - doesn't block UI
    fetchData(id).then(setData);
  }, [id]);
  
  return <div>{data?.name}</div>;
};

// useLayoutEffect - runs BEFORE paint
const TooltipPosition = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const tooltipRef = useRef(null);
  
  useLayoutEffect(() => {
    // Measure DOM before browser paints
    if (tooltipRef.current) {
      const rect = tooltipRef.current.getBoundingClientRect();
      
      // Adjust position if off screen
      let x = rect.left;
      let y = rect.top;
      
      if (x + rect.width > window.innerWidth) {
        x = window.innerWidth - rect.width - 10;
      }
      
      if (y + rect.height > window.innerHeight) {
        y = window.innerHeight - rect.height - 10;
      }
      
      setPosition({ x, y });
    }
  }); // Runs on every render
  
  return (
    <div
      ref={tooltipRef}
      style={{ left: position.x, top: position.y }}
    >
      Tooltip
    </div>
  );
};

// Prevent visual flicker with useLayoutEffect
const AnimatedHeight = ({ isOpen, children }) => {
  const [height, setHeight] = useState(0);
  const contentRef = useRef(null);
  
  useLayoutEffect(() => {
    if (isOpen && contentRef.current) {
      // Measure and set height BEFORE paint
      // Prevents flicker from 0 to auto height
      const contentHeight = contentRef.current.scrollHeight;
      setHeight(contentHeight);
    } else {
      setHeight(0);
    }
  }, [isOpen]);
  
  return (
    <div
      style={{
        height: height,
        overflow: 'hidden',
        transition: 'height 0.3s ease'
      }}
    >
      <div ref={contentRef}>{children}</div>
    </div>
  );
};

// DOM mutation before paint
const ScrollToTop = ({ trigger }) => {
  useLayoutEffect(() => {
    // Scroll happens before user sees the page
    window.scrollTo(0, 0);
  }, [trigger]);
  
  return null;
};

// Comparison: flicker with useEffect
const FlickerExample = () => {
  const [width, setWidth] = useState(0);
  const ref = useRef(null);
  
  // ❌ WRONG: causes visible flicker
  useEffect(() => {
    // Runs AFTER paint - user sees wrong width first
    if (ref.current) {
      setWidth(ref.current.offsetWidth);
    }
  }, []);
  
  return <div ref={ref}>Width: {width}px</div>;
};

// Fixed: no flicker with useLayoutEffect
const NoFlickerExample = () => {
  const [width, setWidth] = useState(0);
  const ref = useRef(null);
  
  // ✅ CORRECT: measures before paint
  useLayoutEffect(() => {
    // Runs BEFORE paint - correct width from start
    if (ref.current) {
      setWidth(ref.current.offsetWidth);
    }
  }, []);
  
  return <div ref={ref}>Width: {width}px</div>;
};

// Focus management with useLayoutEffect
const AutoFocus = ({ shouldFocus }) => {
  const inputRef = useRef(null);
  
  useLayoutEffect(() => {
    if (shouldFocus && inputRef.current) {
      // Focus before paint - no visual jump
      inputRef.current.focus();
    }
  }, [shouldFocus]);
  
  return <input ref={inputRef} />;
};
Rule of thumb: Use useEffect for 99% of cases. Only use useLayoutEffect when you need to read layout (measurements, scroll position) or prevent visual flicker. useLayoutEffect can hurt performance because it blocks painting.

9.4 Data Fetching with useEffect Patterns

Pattern Best Practice Issues to Handle
Basic fetch async/await in effect Loading state, errors
Cleanup Cancellation flag or AbortController setState on unmounted component
Dependencies Include all params used in fetch Stale data, race conditions
Race conditions Ignore results from outdated requests Wrong data displayed
Error handling Try/catch and error state Unhandled rejections
Loading state Boolean flag while fetching Poor UX without feedback

Example: Data fetching patterns

// Basic data fetching
const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch(\`/api/users/\${userId}\`);
        
        if (!response.ok) {
          throw new Error('Failed to fetch user');
        }
        
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]); // Re-fetch when userId changes
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return <div>No user found</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
};

// With cleanup to prevent setState on unmounted
const DataFetchWithCleanup = ({ endpoint }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        const response = await fetch(endpoint);
        const result = await response.json();
        
        if (!cancelled) {
          setData(result);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          console.error(error);
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      cancelled = true;
    };
  }, [endpoint]);
  
  return loading ? <div>Loading...</div> : <div>{JSON.stringify(data)}</div>;
};

// Race condition handling
const SearchResults = ({ query }) => {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }
    
    let cancelled = false;
    setLoading(true);
    
    const search = async () => {
      try {
        const response = await fetch(\`/api/search?q=\${query}\`);
        const data = await response.json();
        
        // Only update if this is still the latest query
        if (!cancelled) {
          setResults(data);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          console.error(error);
          setLoading(false);
        }
      }
    };
    
    search();
    
    // Cleanup: ignore results if query changes
    return () => {
      cancelled = true;
    };
  }, [query]);
  
  return (
    <div>
      {loading && <div>Searching...</div>}
      <ul>
        {results.map(r => (
          <li key={r.id}>{r.title}</li>
        ))}
      </ul>
    </div>
  );
};

// AbortController pattern (modern)
const ModernFetch = ({ url }) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchData = async () => {
      try {
        const response = await fetch(url, {
          signal: controller.signal
        });
        
        const result = await response.json();
        setData(result);
      } catch (err) {
        // Don't set error if aborted
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      }
    };
    
    fetchData();
    
    return () => {
      controller.abort();
    };
  }, [url]);
  
  if (error) return <div>Error: {error}</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Debounced search
const DebouncedSearch = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }
    
    setLoading(true);
    
    // Debounce: wait 500ms after user stops typing
    const timer = setTimeout(async () => {
      try {
        const response = await fetch(\`/api/search?q=\${query}\`);
        const data = await response.json();
        setResults(data);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    }, 500);
    
    // Cleanup: cancel previous timeout
    return () => {
      clearTimeout(timer);
    };
  }, [query]);
  
  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />
      {loading && <div>Searching...</div>}
      <ul>
        {results.map(r => (
          <li key={r.id}>{r.title}</li>
        ))}
      </ul>
    </div>
  );
};

// Multiple parallel requests
const Dashboard = () => {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [notifications, setNotifications] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchAll = async () => {
      try {
        // Fetch all in parallel
        const [userRes, postsRes, notifRes] = await Promise.all([
          fetch('/api/user'),
          fetch('/api/posts'),
          fetch('/api/notifications')
        ]);
        
        const [userData, postsData, notifData] = await Promise.all([
          userRes.json(),
          postsRes.json(),
          notifRes.json()
        ]);
        
        setUser(userData);
        setPosts(postsData);
        setNotifications(notifData);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchAll();
  }, []);
  
  if (loading) return <div>Loading dashboard...</div>;
  
  return (
    <div>
      <h1>Welcome {user?.name}</h1>
      <div>Posts: {posts.length}</div>
      <div>Notifications: {notifications.length}</div>
    </div>
  );
};
Warning: For production apps, consider using dedicated data fetching libraries like SWR or React Query instead of manual useEffect. They handle caching, revalidation, race conditions, and optimistic updates automatically.

9.5 Custom Hooks for Effect Logic

Custom Hook Encapsulates Returns Reusability
useFetch Data fetching logic {data, loading, error} High - any API endpoint
useDebounce Debounced value updates Debounced value High - search, filters
useInterval Timer with cleanup void Medium - polling
useEventListener Event subscription void High - any event
useLocalStorage localStorage sync [value, setValue] High - persistence
useWindowSize Window dimensions {width, height} High - responsive

Example: Reusable custom hooks

// useFetch - reusable data fetching
const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    if (!url) return;
    
    const controller = new AbortController();
    
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url, {
          signal: controller.signal
        });
        
        if (!response.ok) {
          throw new Error(\`HTTP error! status: \${response.status}\`);
        }
        
        const result = await response.json();
        setData(result);
        setError(null);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
    
    return () => controller.abort();
  }, [url]);
  
  return { data, loading, error };
};

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

// useDebounce - delay value updates
const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
};

// Usage
const SearchComponent = () => {
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 500);
  const { data } = useFetch(\`/api/search?q=\${debouncedSearch}\`);
  
  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      <div>{JSON.stringify(data)}</div>
    </div>
  );
};

// useInterval - declarative setInterval
const useInterval = (callback, delay) => {
  const savedCallback = useRef();
  
  // Remember latest callback
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  
  // Set up interval
  useEffect(() => {
    if (delay === null) return;
    
    const tick = () => {
      savedCallback.current();
    };
    
    const id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
};

// Usage
const Clock = () => {
  const [time, setTime] = useState(new Date());
  
  useInterval(() => {
    setTime(new Date());
  }, 1000);
  
  return <div>{time.toLocaleTimeString()}</div>;
};

// useEventListener - declarative addEventListener
const useEventListener = (eventName, handler, element = window) => {
  const savedHandler = useRef();
  
  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);
  
  useEffect(() => {
    const isSupported = element && element.addEventListener;
    if (!isSupported) return;
    
    const eventListener = (event) => savedHandler.current(event);
    element.addEventListener(eventName, eventListener);
    
    return () => {
      element.removeEventListener(eventName, eventListener);
    };
  }, [eventName, element]);
};

// Usage
const KeyLogger = () => {
  const [key, setKey] = useState('');
  
  useEventListener('keydown', (e) => {
    setKey(e.key);
  });
  
  return <div>Last key: {key}</div>;
};

// useLocalStorage - sync state with localStorage
const 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 {
      const valueToStore = value instanceof Function
        ? value(storedValue)
        : value;
      
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
};

// Usage
const ThemeToggle = () => {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current: {theme}
    </button>
  );
};

// useWindowSize - track window dimensions
const useWindowSize = () => {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return size;
};

// Usage
const ResponsiveComponent = () => {
  const { width } = useWindowSize();
  
  return (
    <div>
      {width < 768 ? 'Mobile' : 'Desktop'} - {width}px
    </div>
  );
};
Note: Custom hooks let you extract and reuse effect logic across components. Name them with use prefix and follow all hooks rules. They can use other hooks internally.

9.6 Effect Performance and Optimization

Optimization Technique Impact When to Use
Proper dependencies Only include what changes Avoid unnecessary runs Always
useMemo deps Memoize object/array deps Prevent extra effect runs Object dependencies
useCallback deps Memoize function deps Stable function reference Function dependencies
Split effects One effect per concern Run only what's needed Multiple responsibilities
Functional updates setState(prev => ...) Remove state from deps State-dependent updates
Debouncing Delay effect execution Reduce frequency Frequent changes (input)
Throttling Limit execution rate Control frequency Continuous events (scroll)

Example: Effect performance optimizations

// ❌ BAD: Unnecessary effect runs
const BadDeps = ({ user }) => {
  useEffect(() => {
    console.log(user.name);
  }, [user]); // Runs every time user object changes (even if name is same)
  
  return null;
};

// ✅ GOOD: Only depend on what you use
const GoodDeps = ({ user }) => {
  useEffect(() => {
    console.log(user.name);
  }, [user.name]); // Only runs when name actually changes
  
  return null;
};

// ❌ BAD: Object dependency causes extra runs
const ParentBad = () => {
  const [count, setCount] = useState(0);
  
  // New object every render!
  const config = { apiUrl: 'https://api.example.com' };
  
  return <ChildBad config={config} />;
};

const ChildBad = ({ config }) => {
  useEffect(() => {
    fetch(config.apiUrl); // Runs every render!
  }, [config]);
  
  return null;
};

// ✅ GOOD: Memoize object dependency
const ParentGood = () => {
  const [count, setCount] = useState(0);
  
  // Same object reference across renders
  const config = useMemo(
    () => ({ apiUrl: 'https://api.example.com' }),
    []
  );
  
  return <ChildGood config={config} />;
};

const ChildGood = ({ config }) => {
  useEffect(() => {
    fetch(config.apiUrl); // Only runs once
  }, [config]);
  
  return null;
};

// ❌ BAD: Multiple concerns in one effect
const BadMultiEffect = ({ userId, theme }) => {
  useEffect(() => {
    // Fetch user data
    fetchUser(userId).then(setUser);
    
    // Apply theme
    document.body.className = theme;
    
    // Both run on every userId OR theme change!
  }, [userId, theme]);
  
  return null;
};

// ✅ GOOD: Split into separate effects
const GoodMultiEffect = ({ userId, theme }) => {
  // Effect 1: User data (only re-runs when userId changes)
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  // Effect 2: Theme (only re-runs when theme changes)
  useEffect(() => {
    document.body.className = theme;
  }, [theme]);
  
  return null;
};

// ❌ BAD: State in dependencies
const BadStateDep = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1); // Uses stale count
    }, 1000);
    
    return () => clearInterval(timer);
  }, [count]); // Re-creates interval on every count change!
  
  return <div>{count}</div>;
};

// ✅ GOOD: Functional update removes dependency
const GoodStateDep = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1); // Functional update
    }, 1000);
    
    return () => clearInterval(timer);
  }, []); // No dependencies - interval created once
  
  return <div>{count}</div>;
};

// Debouncing to reduce effect frequency
const SearchWithDebounce = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    // Wait for user to stop typing
    const timer = setTimeout(() => {
      if (searchTerm) {
        fetch(\`/api/search?q=\${searchTerm}\`)
          .then(r => r.json())
          .then(setResults);
      }
    }, 500); // Debounce 500ms
    
    return () => clearTimeout(timer);
  }, [searchTerm]);
  
  return (
    <div>
      <input
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
      />
      <ul>
        {results.map(r => (
          <li key={r.id}>{r.title}</li>
        ))}
      </ul>
    </div>
  );
};

// Throttling for continuous events
const ScrollTracker = () => {
  const [scrollY, setScrollY] = useState(0);
  
  useEffect(() => {
    let ticking = false;
    
    const handleScroll = () => {
      if (!ticking) {
        window.requestAnimationFrame(() => {
          setScrollY(window.scrollY);
          ticking = false;
        });
        
        ticking = true;
      }
    };
    
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);
  
  return <div>Scroll position: {scrollY}px</div>;
};

// Conditional effect execution
const ConditionalEffect = ({ shouldFetch, userId }) => {
  useEffect(() => {
    // Early return to skip effect
    if (!shouldFetch) return;
    
    fetchUser(userId).then(setUser);
  }, [shouldFetch, userId]);
  
  return null;
};

Effect Best Practices Checklist

10. Performance Optimization Techniques

10.1 React.memo and Component Memoization

Concept Description When to Use Performance Impact
React.memo HOC that memoizes component result Expensive renders, pure components Skips re-render if props unchanged
Shallow comparison Default props comparison (===) Primitive props Fast, works for most cases
Custom comparison Custom arePropsEqual function Complex props, deep equality Slower, use judiciously
When NOT to use Simple components, always different props Overhead > benefit Can hurt performance
Props Type Memo Behavior Solution
Primitives Compared by value Works perfectly
Objects Compared by reference useMemo to stabilize
Arrays Compared by reference useMemo to stabilize
Functions Compared by reference useCallback to stabilize
Children prop New reference each render Extract to separate component

Example: React.memo usage patterns

// Basic React.memo usage
const ExpensiveComponent = React.memo(({ data }) => {
  console.log('Rendering ExpensiveComponent');
  
  // Expensive computation or large render tree
  const processedData = data.map(item => {
    // Complex processing
    return item.value * 2;
  });
  
  return (
    <div>
      {processedData.map(val => (
        <div key={val}>{val}</div>
      ))}
    </div>
  );
});

// Parent component
const Parent = () => {
  const [count, setCount] = useState(0);
  const [data] = useState([1, 2, 3, 4, 5]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      {/* ExpensiveComponent won't re-render when count changes */}
      <ExpensiveComponent data={data} />
    </div>
  );
};

// ❌ BAD: memo doesn't help with new object references
const BadMemo = React.memo(({ config }) => {
  return <div>{config.name}</div>;
});

const ParentBad = () => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* Re-renders every time - new object! */}
      <BadMemo config={{ name: 'Test' }} />
    </div>
  );
};

// ✅ GOOD: stabilize object with useMemo
const GoodMemo = React.memo(({ config }) => {
  return <div>{config.name}</div>;
});

const ParentGood = () => {
  const [count, setCount] = useState(0);
  
  const config = useMemo(() => ({ name: 'Test' }), []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* Only renders once! */}
      <GoodMemo config={config} />
    </div>
  );
};

// Custom comparison function
const UserCard = React.memo(
  ({ user, onEdit }) => {
    return (
      <div>
        <h3>{user.name}</h3>
        <p>{user.email}</p>
        <button onClick={() => onEdit(user.id)}>Edit</button>
      </div>
    );
  },
  (prevProps, nextProps) => {
    // Custom comparison: only re-render if user.id or user.name changed
    return (
      prevProps.user.id === nextProps.user.id &&
      prevProps.user.name === nextProps.user.name
    );
    // Return true to skip render, false to render
  }
);

// When NOT to use React.memo
const SimpleComponent = ({ text }) => {
  return <div>{text}</div>;
};
// No need for memo - component is very simple

const AlwaysChanging = ({ timestamp }) => {
  return <div>{timestamp}</div>;
};
// No need for memo - timestamp always changes

// Memoizing list items
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
  console.log('Rendering todo:', todo.id);
  
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
});

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  
  // Stabilize callback functions
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, []);
  
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
};
Note: React.memo only checks props changes. It doesn't prevent re-renders from state/context changes within the component. Use it for expensive components with stable props.

10.2 useMemo for Expensive Calculations

Use Case Without useMemo With useMemo Benefit
Expensive calculation Runs every render Cached until deps change Saves computation time
Array filtering/sorting Creates new array every render Reuses array if deps unchanged Prevents unnecessary work
Object creation New reference every render Stable reference Prevents child re-renders
Derived state Computed on each render Computed only when needed Performance optimization
When to Use When NOT to Use
Expensive calculations (loops, recursion) Simple calculations (arithmetic)
Processing large arrays/objects Small data sets (< 100 items)
Stabilizing references for child props Values that always change
Complex transformations/filtering Trivial operations

Example: useMemo patterns

// Expensive calculation
const ProductList = ({ products, filter }) => {
  // Without useMemo: runs on EVERY render (bad!)
  // const filtered = products.filter(p => p.category === filter);
  
  // With useMemo: only runs when products or filter changes
  const filteredProducts = useMemo(() => {
    console.log('Filtering products...');
    return products.filter(p => p.category === filter);
  }, [products, filter]);
  
  return (
    <ul>
      {filteredProducts.map(p => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
};

// Complex derived state
const Dashboard = ({ data }) => {
  const statistics = useMemo(() => {
    console.log('Computing statistics...');
    
    const total = data.reduce((sum, item) => sum + item.value, 0);
    const average = total / data.length;
    const max = Math.max(...data.map(item => item.value));
    const min = Math.min(...data.map(item => item.value));
    
    return { total, average, max, min };
  }, [data]);
  
  return (
    <div>
      <p>Total: {statistics.total}</p>
      <p>Average: {statistics.average}</p>
      <p>Max: {statistics.max}</p>
      <p>Min: {statistics.min}</p>
    </div>
  );
};

// Sorting large lists
const SortableTable = ({ data, sortBy, sortOrder }) => {
  const sortedData = useMemo(() => {
    console.log('Sorting data...');
    
    return [...data].sort((a, b) => {
      const aVal = a[sortBy];
      const bVal = b[sortBy];
      
      if (typeof aVal === 'string') {
        return sortOrder === 'asc'
          ? aVal.localeCompare(bVal)
          : bVal.localeCompare(aVal);
      }
      
      return sortOrder === 'asc' ? aVal - bVal : bVal - aVal;
    });
  }, [data, sortBy, sortOrder]);
  
  return (
    <table>
      {sortedData.map(row => (
        <tr key={row.id}>
          <td>{row.name}</td>
        </tr>
      ))}
    </table>
  );
};

// Stabilizing object references
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // ❌ BAD: new object every render
  // const config = { theme: 'dark', lang: 'en' };
  
  // ✅ GOOD: stable reference
  const config = useMemo(
    () => ({ theme: 'dark', lang: 'en' }),
    []
  );
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <MemoizedChild config={config} />
    </div>
  );
};

// When NOT to use useMemo
const Bad = ({ a, b }) => {
  // ❌ Overkill - simple calculation
  const sum = useMemo(() => a + b, [a, b]);
  
  // ✅ Just do it directly
  const betterSum = a + b;
  
  return <div>{sum}</div>;
};

// Search and filter with multiple criteria
const AdvancedSearch = ({ items, search, filters }) => {
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    
    return items.filter(item => {
      // Text search
      if (search && !item.name.toLowerCase().includes(search.toLowerCase())) {
        return false;
      }
      
      // Category filter
      if (filters.category && item.category !== filters.category) {
        return false;
      }
      
      // Price range
      if (item.price < filters.minPrice || item.price > filters.maxPrice) {
        return false;
      }
      
      return true;
    });
  }, [items, search, filters]);
  
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredItems]);
  
  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name} - ${item.price}</li>
      ))}
    </ul>
  );
};

// Recursive computation
const TreeView = ({ node }) => {
  const descendantCount = useMemo(() => {
    const countDescendants = (n) => {
      if (!n.children) return 0;
      return n.children.length + 
        n.children.reduce((sum, child) => sum + countDescendants(child), 0);
    };
    return countDescendants(node);
  }, [node]);
  
  return <div>Descendants: {descendantCount}</div>;
};
Warning: Don't overuse useMemo! It has overhead (memory + comparison). Profile first, optimize later. Most calculations are fast enough without memoization.

10.3 useCallback for Function Memoization

Scenario Without useCallback With useCallback Impact
Function prop to memo component New function = child re-renders Stable function = no re-render Prevents unnecessary renders
Function in useEffect deps Effect runs every render Effect runs only when needed Reduces side effects
Event handlers New function every render Stable reference Slight memory improvement
When to Use useCallback When NOT to Use
Function passed to memoized child Simple event handlers not passed down
Function in useEffect dependencies Function only used once
Function passed to many children Non-memoized children
Optimization after profiling Premature optimization

Example: useCallback patterns

// Basic useCallback usage
const Parent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  // ❌ BAD: new function every render
  // const handleClick = () => {
  //   console.log(count);
  // };
  
  // ✅ GOOD: stable function reference
  const handleClick = useCallback(() => {
    console.log(count);
  }, [count]); // Re-created only when count changes
  
  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* Child won't re-render when text changes */}
      <MemoizedChild onClick={handleClick} />
    </div>
  );
};

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
});

// Function in useEffect dependencies
const DataFetcher = ({ userId }) => {
  const [data, setData] = useState(null);
  
  // Stable fetchData function
  const fetchData = useCallback(async () => {
    const response = await fetch(\`/api/users/\${userId}\`);
    const result = await response.json();
    setData(result);
  }, [userId]);
  
  useEffect(() => {
    fetchData();
  }, [fetchData]); // Only runs when fetchData changes (i.e., when userId changes)
  
  return <div>{data?.name}</div>;
};

// List operations with callbacks
const TodoList = () => {
  const [todos, setTodos] = useState([]);
  
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, []); // Empty deps - uses functional update
  
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);
  
  const handleEdit = useCallback((id, newText) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, text: newText } : todo
    ));
  }, []);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
          onEdit={handleEdit}
        />
      ))}
    </ul>
  );
};

// When NOT to use useCallback
const SimpleComponent = () => {
  const [count, setCount] = useState(0);
  
  // ❌ Unnecessary - not passed to memoized child
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  // ✅ Just use inline
  return (
    <button onClick={() => setCount(c => c + 1)}>
      {count}
    </button>
  );
};

// Combining useCallback with other hooks
const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const search = useCallback(async (searchQuery) => {
    if (!searchQuery) {
      setResults([]);
      return;
    }
    
    const response = await fetch(\`/api/search?q=\${searchQuery}\`);
    const data = await response.json();
    setResults(data);
  }, []);
  
  // Debounced search
  useEffect(() => {
    const timer = setTimeout(() => {
      search(query);
    }, 300);
    
    return () => clearTimeout(timer);
  }, [query, search]);
  
  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <ul>
        {results.map(r => (
          <li key={r.id}>{r.title}</li>
        ))}
      </ul>
    </div>
  );
};

// useCallback with parameters from closure
const ItemList = ({ items, onUpdate }) => {
  const [filter, setFilter] = useState('');
  
  // ❌ BAD: depends on filter but not in deps
  // const handleUpdate = useCallback((id, value) => {
  //   if (filter === 'active') {
  //     onUpdate(id, value);
  //   }
  // }, []); // Missing filter!
  
  // ✅ GOOD: includes all dependencies
  const handleUpdate = useCallback((id, value) => {
    if (filter === 'active') {
      onUpdate(id, value);
    }
  }, [filter, onUpdate]);
  
  return (
    <ul>
      {items.map(item => (
        <Item key={item.id} item={item} onUpdate={handleUpdate} />
      ))}
    </ul>
  );
};
Note: useCallback returns memoized function, useMemo returns memoized value. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

10.4 Code Splitting with React.lazy and Suspense

Technique Description Bundle Impact Use Case
React.lazy Dynamic import for components Separate chunk per lazy component Route-based splitting
Suspense Loading fallback for lazy components No impact Show loading state
Route-based Split by routes/pages One chunk per route Most common pattern
Component-based Split heavy components Smaller chunks Modals, tabs, charts
Library splitting Separate vendor chunks Better caching Large dependencies

Example: Code splitting patterns

// Basic React.lazy usage
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
};

// Route-based code splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom';

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

const AppRouter = () => {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

// Conditional lazy loading
const AdminPanel = lazy(() => import('./AdminPanel'));

const App = ({ user }) => {
  return (
    <div>
      {user.isAdmin && (
        <Suspense fallback={<div>Loading admin panel...</div>}>
          <AdminPanel />
        </Suspense>
      )}
    </div>
  );
};

// Named exports with lazy
const LazyComponent = lazy(() => 
  import('./MyComponent').then(module => ({
    default: module.MyNamedComponent
  }))
);

// Preloading lazy components
const HeavyChart = lazy(() => import('./HeavyChart'));

// Preload function
const preloadChart = () => {
  import('./HeavyChart');
};

const Dashboard = () => {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      {/* Preload on hover */}
      <button
        onClick={() => setShowChart(true)}
        onMouseEnter={preloadChart}
      >
        Show Chart
      </button>
      
      {showChart && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
};

// Modal lazy loading
const Modal = lazy(() => import('./Modal'));

const App = () => {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      
      {isOpen && (
        <Suspense fallback={null}>
          <Modal onClose={() => setIsOpen(false)} />
        </Suspense>
      )}
    </div>
  );
};

// Tab-based splitting
const Tab1 = lazy(() => import('./tabs/Tab1'));
const Tab2 = lazy(() => import('./tabs/Tab2'));
const Tab3 = lazy(() => import('./tabs/Tab3'));

const TabbedInterface = () => {
  const [activeTab, setActiveTab] = useState('tab1');
  
  return (
    <div>
      <div>
        <button onClick={() => setActiveTab('tab1')}>Tab 1</button>
        <button onClick={() => setActiveTab('tab2')}>Tab 2</button>
        <button onClick={() => setActiveTab('tab3')}>Tab 3</button>
      </div>
      
      <Suspense fallback={<div>Loading tab...</div>}>
        {activeTab === 'tab1' && <Tab1 />}
        {activeTab === 'tab2' && <Tab2 />}
        {activeTab === 'tab3' && <Tab3 />}
      </Suspense>
    </div>
  );
};

// Error boundary with lazy loading
class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Failed to load component</div>;
    }
    return this.props.children;
  }
}

const AppWithErrorHandling = () => {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </ErrorBoundary>
  );
};

// Webpack magic comments for chunk naming
const Dashboard = lazy(() =>
  import(/* webpackChunkName: "dashboard" */ './Dashboard')
);

const AdminPanel = lazy(() =>
  import(/* webpackChunkName: "admin" */ './AdminPanel')
);

// Prefetching/preloading
const Settings = lazy(() =>
  import(
    /* webpackChunkName: "settings" */
    /* webpackPrefetch: true */
    './Settings'
  )
);
Note: Code splitting works best for route-based splits and heavy components (charts, editors, admin panels). Don't split every component - bundle overhead can outweigh benefits for small components.

10.5 Bundle Analysis and Optimization Strategies

Tool Purpose Install/Usage
webpack-bundle-analyzer Visualize bundle contents npm i -D webpack-bundle-analyzer
source-map-explorer Analyze source maps npm i -D source-map-explorer
Bundle Buddy Find duplicate dependencies Upload webpack stats.json
Lighthouse Performance audit Chrome DevTools
Optimization Technique Impact
Tree shaking Remove unused exports Smaller bundle (10-30%)
Minification Compress code 40-60% size reduction
Compression (gzip/brotli) Server-side compression 60-80% transfer size reduction
Dynamic imports Load code on demand Faster initial load
Remove unused deps Audit package.json Variable savings
Lighter alternatives Replace heavy libraries Significant size reduction

Example: Bundle optimization techniques

// package.json - analyze bundle
{
  "scripts": {
    "analyze": "source-map-explorer 'build/static/js/*.js'",
    "build:analyze": "npm run build && npm run analyze"
  }
}

// webpack.config.js - bundle analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
};

// Tree shaking - import only what you need
// ❌ BAD: imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);

// ✅ GOOD: import specific function
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);

// Or use lodash-es for better tree shaking
import { debounce } from 'lodash-es';

// Replace heavy libraries with lighter alternatives
// ❌ Moment.js (large)
import moment from 'moment';

// ✅ date-fns (smaller, tree-shakeable)
import { format, parseISO } from 'date-fns';

// ✅ dayjs (smallest)
import dayjs from 'dayjs';

// Remove unused dependencies
// Run: npm ls
// Check: npx depcheck

// Dynamic imports for heavy dependencies
const loadChartLibrary = async () => {
  const Chart = await import('chart.js');
  return Chart.default;
};

// Optimize images
// Use next/image or similar
import Image from 'next/image';

<Image
  src="/photo.jpg"
  width={500}
  height={300}
  loading="lazy"
  alt="Photo"
/>

// Code splitting by route
const routes = [
  {
    path: '/',
    component: lazy(() => import('./pages/Home'))
  },
  {
    path: '/dashboard',
    component: lazy(() => import('./pages/Dashboard'))
  }
];

// Vendor chunk separation (webpack)
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        priority: 10
      },
      common: {
        minChunks: 2,
        priority: 5,
        reuseExistingChunk: true
      }
    }
  }
}

// Preload critical resources
<link rel="preload" as="script" href="/main.js" />
<link rel="prefetch" href="/secondary.js" />

// Compression middleware (Express)
const compression = require('compression');
app.use(compression());

// Service worker for caching
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

// Remove console logs in production
// babel-plugin-transform-remove-console
{
  "plugins": [
    ["transform-remove-console", { "exclude": ["error", "warn"] }]
  ]
}
Warning: Always analyze your bundle before optimizing. Focus on the biggest chunks first. Remember: premature optimization is the root of all evil!

10.6 Re-render Optimization and Profiling

Tool Purpose How to Use
React DevTools Profiler Record and analyze renders Click "Profiler" tab, record session
Highlight Updates Visualize re-renders Settings → "Highlight updates"
Component Stack See render cause Click component in Profiler
Why Did You Render Debug unnecessary renders npm package with detailed logs
Common Issue Symptom Solution
Inline functions Child re-renders unnecessarily useCallback + React.memo
Inline objects Props always different useMemo for stable reference
Context changes All consumers re-render Split contexts, memoize value
State too high Entire tree re-renders Move state closer to usage
Large lists Slow rendering Virtualization, pagination
No keys/bad keys Wrong items update Stable unique keys

Example: Debugging and fixing re-renders

// Install why-did-you-render
import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

// Use in component
const MyComponent = ({ value }) => {
  return <div>{value}</div>;
};

MyComponent.whyDidYouRender = true;

// Finding render causes with useEffect
const DebugComponent = ({ prop1, prop2, prop3 }) => {
  useEffect(() => {
    console.log('Component rendered');
    console.log('prop1:', prop1);
    console.log('prop2:', prop2);
    console.log('prop3:', prop3);
  });
  
  return <div>Debug</div>;
};

// Custom hook to track prop changes
const useWhyDidYouUpdate = (name, props) => {
  const previousProps = useRef();
  
  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps = {};
      
      allKeys.forEach(key => {
        if (previousProps.current[key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current[key],
            to: props[key]
          };
        }
      });
      
      if (Object.keys(changedProps).length > 0) {
        console.log('[why-did-you-update]', name, changedProps);
      }
    }
    
    previousProps.current = props;
  });
};

// Usage
const MyComponent = (props) => {
  useWhyDidYouUpdate('MyComponent', props);
  return <div>{props.value}</div>;
};

// Fix: Inline object props
// ❌ BAD
const Parent = () => {
  return <Child style={{ color: 'red' }} />; // New object every render
};

// ✅ GOOD
const Parent = () => {
  const style = useMemo(() => ({ color: 'red' }), []);
  return <Child style={style} />;
};

// Fix: Context re-renders
// ❌ BAD: value changes every render
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// ✅ GOOD: stable value reference
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

// Fix: State colocation
// ❌ BAD: state too high
const App = () => {
  const [formData, setFormData] = useState({});
  
  return (
    <div>
      <Header /> {/* Re-renders on formData change */}
      <Sidebar /> {/* Re-renders on formData change */}
      <Form data={formData} onChange={setFormData} />
    </div>
  );
};

// ✅ GOOD: state colocated
const App = () => {
  return (
    <div>
      <Header />
      <Sidebar />
      <FormContainer /> {/* State inside here */}
    </div>
  );
};

const FormContainer = () => {
  const [formData, setFormData] = useState({});
  return <Form data={formData} onChange={setFormData} />;
};

// Profiling with React.Profiler API
const App = () => {
  const onRenderCallback = (
    id, // component name
    phase, // "mount" or "update"
    actualDuration, // time spent rendering
    baseDuration, // estimated time without memoization
    startTime, // when started
    commitTime, // when committed
    interactions // Set of interactions
  ) => {
    console.log(\`\${id} took \${actualDuration}ms to render\`);
  };
  
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyApp />
    </Profiler>
  );
};

Performance Optimization Checklist

11. Error Handling and Error Boundaries

11.1 Error Boundary Components and Implementation

Concept Description Catches Does NOT Catch
Error Boundary Class component that catches errors in child tree Render errors, lifecycle errors Event handlers, async, SSR
Placement Wrap components that might error All descendants Errors in boundary itself
Granularity Multiple boundaries for isolation Specific sections only Unrelated components
Error Type Caught by Boundary? Handling Approach
Render errors ✅ Yes Error boundary
Lifecycle methods ✅ Yes Error boundary
Event handlers ❌ No try/catch in handler
Async code (setTimeout) ❌ No try/catch in callback
Server-side rendering ❌ No Server error handling
Error in boundary itself ❌ No Parent error boundary

Example: Error boundary implementation

// Basic error boundary class component
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    // Update state so next render shows fallback UI
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Log error to reporting service
    console.error('Error caught by boundary:', error, errorInfo);
    // logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Something went wrong.</h1>
          <details>
            <summary>Error details</summary>
            <pre>{this.state.error.toString()}</pre>
          </details>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage - wrap components
const App = () => (
  <ErrorBoundary>
    <Header />
    <Main />
    <Footer />
  </ErrorBoundary>
);

// Multiple boundaries for isolation
const Dashboard = () => (
  <div>
    <ErrorBoundary>
      <Sidebar />
    </ErrorBoundary>
    
    <ErrorBoundary>
      <MainContent />
    </ErrorBoundary>
    
    <ErrorBoundary>
      <Widgets />
    </ErrorBoundary>
  </div>
);

// Enhanced error boundary with retry
class ErrorBoundaryWithRetry extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null,
      errorInfo: null
    };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    this.setState({ errorInfo });
    console.error('Error:', error);
    console.error('Component stack:', errorInfo.componentStack);
  }
  
  handleReset = () => {
    this.setState({ 
      hasError: false, 
      error: null,
      errorInfo: null
    });
  };
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h2>Oops! Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={this.handleReset}>
            Try Again
          </button>
          {process.env.NODE_ENV === 'development' && (
            <details>
              <summary>Stack trace</summary>
              <pre>{this.state.errorInfo?.componentStack}</pre>
            </details>
          )}
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Custom fallback component
const ErrorFallback = ({ error, resetError }) => (
  <div role="alert">
    <h2>Something went wrong</h2>
    <pre style={{ color: 'red' }}>{error.message}</pre>
    <button onClick={resetError}>Try again</button>
  </div>
);

class ErrorBoundaryWithFallback extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    this.props.onError?.(error, errorInfo);
  }
  
  resetError = () => {
    this.setState({ hasError: false, error: null });
  };
  
  render() {
    if (this.state.hasError) {
      const FallbackComponent = this.props.fallback || ErrorFallback;
      return (
        <FallbackComponent 
          error={this.state.error}
          resetError={this.resetError}
        />
      );
    }
    
    return this.props.children;
  }
}

// Usage with custom fallback
const App = () => (
  <ErrorBoundaryWithFallback
    fallback={CustomErrorPage}
    onError={(error, info) => logToService(error, info)}
  >
    <Routes />
  </ErrorBoundaryWithFallback>
);

// Component that will error
const BuggyComponent = () => {
  const [count, setCount] = useState(0);
  
  if (count > 3) {
    throw new Error('Count exceeded limit!');
  }
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
};
Warning: Error boundaries do NOT catch errors in event handlers, async code (setTimeout, promises), server-side rendering, or errors thrown in the error boundary itself. Use try/catch for those cases.

11.2 componentDidCatch and getDerivedStateFromError

Method When Called Purpose Can Update State
getDerivedStateFromError During render phase Update state to show fallback UI ✅ Yes (return new state)
componentDidCatch After commit phase Log errors, report to service ⚠️ Yes but triggers extra render
Parameter Available In Contains
error Both methods Error object with message, stack
errorInfo componentDidCatch only componentStack - where error occurred

Example: Using both lifecycle methods

class AdvancedErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      errorCount: 0
    };
  }
  
  // Static method - called during render phase
  // MUST be pure - no side effects!
  static getDerivedStateFromError(error) {
    // Update state to trigger fallback UI
    return {
      hasError: true,
      error: error
    };
  }
  
  // Called after render in commit phase
  // Can have side effects - logging, reporting
  componentDidCatch(error, errorInfo) {
    // errorInfo.componentStack shows component hierarchy
    console.error('Error caught:', error);
    console.error('Component stack:', errorInfo.componentStack);
    
    // Update state to store error details
    this.setState(prevState => ({
      errorInfo,
      errorCount: prevState.errorCount + 1
    }));
    
    // Report to error tracking service
    if (this.props.onError) {
      this.props.onError(error, errorInfo);
    }
    
    // Send to monitoring service
    // Sentry.captureException(error, { contexts: { react: errorInfo } });
  }
  
  componentDidUpdate(prevProps, prevState) {
    // Clear error if children change (new route, etc)
    if (this.state.hasError && prevProps.children !== this.props.children) {
      this.setState({ hasError: false, error: null, errorInfo: null });
    }
  }
  
  handleReset = () => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null
    });
  };
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h1>Something went wrong</h1>
          <p>{this.state.error?.message}</p>
          <p>Error count: {this.state.errorCount}</p>
          
          {process.env.NODE_ENV === 'development' && (
            <>
              <h3>Error Stack:</h3>
              <pre>{this.state.error?.stack}</pre>
              
              <h3>Component Stack:</h3>
              <pre>{this.state.errorInfo?.componentStack}</pre>
            </>
          )}
          
          <button onClick={this.handleReset}>
            Try Again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Error boundary with different fallbacks based on error type
class SmartErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Categorize errors
    const errorType = this.categorizeError(error);
    
    console.error(\`[\${errorType}] Error:`, error);
    console.error('Stack:', errorInfo.componentStack);
    
    // Report with category
    this.reportError(error, errorInfo, errorType);
  }
  
  categorizeError(error) {
    if (error.message.includes('fetch')) return 'NETWORK';
    if (error.message.includes('undefined')) return 'RUNTIME';
    if (error.name === 'ChunkLoadError') return 'CHUNK_LOAD';
    return 'UNKNOWN';
  }
  
  reportError(error, errorInfo, category) {
    // Send to monitoring service
    fetch('/api/errors', {
      method: 'POST',
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        category,
        timestamp: Date.now(),
        userAgent: navigator.userAgent
      })
    });
  }
  
  render() {
    if (this.state.hasError) {
      const errorType = this.categorizeError(this.state.error);
      
      // Different UI based on error type
      switch (errorType) {
        case 'NETWORK':
          return <NetworkErrorFallback />;
        case 'CHUNK_LOAD':
          return <ChunkLoadErrorFallback />;
        default:
          return <GenericErrorFallback error={this.state.error} />;
      }
    }
    
    return this.props.children;
  }
}
Note: Use getDerivedStateFromError for UI updates (pure), and componentDidCatch for side effects like logging. getDerivedStateFromError is called during render, so it must be pure.

11.3 useErrorHandler Hook Patterns

Pattern Description Use Case
Custom hook Throw errors to boundary from hooks Event handlers, async operations
Error state Store error in state, throw in render Propagate to error boundary
Library (react-error-boundary) Pre-built hooks and components Production-ready solution

Example: useErrorHandler custom hook

// Custom useErrorHandler hook
const useErrorHandler = () => {
  const [error, setError] = useState(null);
  
  // Throw error during render to trigger error boundary
  if (error) {
    throw error;
  }
  
  return setError;
};

// Usage in event handlers
const MyComponent = () => {
  const handleError = useErrorHandler();
  
  const handleClick = async () => {
    try {
      await somethingThatMightFail();
    } catch (error) {
      handleError(error); // Propagates to error boundary
    }
  };
  
  return <button onClick={handleClick}>Click me</button>;
};

// Using with data fetching
const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const handleError = useErrorHandler();
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch(\`/api/users/\${userId}\`);
        if (!response.ok) throw new Error('Failed to fetch user');
        const data = await response.json();
        setUser(data);
      } catch (error) {
        handleError(error); // Triggers error boundary
      }
    };
    
    fetchUser();
  }, [userId, handleError]);
  
  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
};

// Using react-error-boundary library
import { useErrorHandler, ErrorBoundary } from 'react-error-boundary';

const DataFetcher = () => {
  const handleError = useErrorHandler();
  
  useEffect(() => {
    fetchData()
      .catch(handleError); // Automatically propagates to boundary
  }, [handleError]);
  
  return <div>Data</div>;
};

// Error boundary with hook from library
const App = () => (
  <ErrorBoundary
    FallbackComponent={ErrorFallback}
    onError={(error, errorInfo) => {
      console.error('Error:', error);
      console.error('Info:', errorInfo);
    }}
    onReset={() => {
      // Reset app state
      window.location.href = '/';
    }}
  >
    <DataFetcher />
  </ErrorBoundary>
);

// Advanced hook with error context
const ErrorContext = createContext();

const ErrorProvider = ({ children }) => {
  const [errors, setErrors] = useState([]);
  
  const addError = useCallback((error) => {
    setErrors(prev => [...prev, {
      id: Date.now(),
      message: error.message,
      timestamp: new Date()
    }]);
  }, []);
  
  const clearErrors = useCallback(() => {
    setErrors([]);
  }, []);
  
  return (
    <ErrorContext.Provider value={{ errors, addError, clearErrors }}>
      {children}
    </ErrorContext.Provider>
  );
};

const useError = () => {
  const context = useContext(ErrorContext);
  if (!context) {
    throw new Error('useError must be used within ErrorProvider');
  }
  return context;
};

// Usage
const MyComponent = () => {
  const { addError, errors } = useError();
  
  const handleAction = async () => {
    try {
      await riskyOperation();
    } catch (error) {
      addError(error);
    }
  };
  
  return (
    <div>
      {errors.map(err => (
        <div key={err.id} className="error-toast">
          {err.message}
        </div>
      ))}
      <button onClick={handleAction}>Action</button>
    </div>
  );
};

// Retrying with hook
const useAsyncError = () => {
  const [, setError] = useState();
  
  return useCallback(
    (error) => {
      setError(() => {
        throw error;
      });
    },
    []
  );
};

const ComponentWithRetry = () => {
  const throwError = useAsyncError();
  const [retryCount, setRetryCount] = useState(0);
  
  const fetchData = async () => {
    try {
      const data = await fetch('/api/data');
      if (!data.ok) throw new Error('Fetch failed');
      return data.json();
    } catch (error) {
      if (retryCount < 3) {
        setRetryCount(retryCount + 1);
      } else {
        throwError(error);
      }
    }
  };
  
  useEffect(() => {
    fetchData();
  }, [retryCount]);
  
  return <div>Content</div>;
};
Note: Consider using the react-error-boundary library for production apps. It provides battle-tested hooks and components with advanced features like retry, reset keys, and more.

11.4 Async Error Handling and Promise Rejections

Async Scenario Error Boundary? Solution
useEffect fetch ❌ No try/catch + error state or hook
Event handler async ❌ No try/catch + error state
setTimeout/setInterval ❌ No try/catch in callback
Promise.catch ❌ No .catch() or try/catch with await
Unhandled rejection ❌ No Global handler + error boundary

Example: Async error handling patterns

// Async/await with try/catch
const DataComponent = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch('/api/data');
        
        if (!response.ok) {
          throw new Error(\`HTTP error! status: \${response.status}\`);
        }
        
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
        console.error('Fetch error:', err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, []);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Promise chains with .catch()
const PromiseChainComponent = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => setData(data))
      .catch(error => {
        console.error('Error:', error);
        setError(error.message);
      });
  }, []);
  
  if (error) return <div>Error: {error}</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Global unhandled rejection handler
const setupGlobalErrorHandling = () => {
  // Unhandled promise rejections
  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled promise rejection:', event.reason);
    
    // Prevent default browser handling
    event.preventDefault();
    
    // Report to monitoring service
    reportError({
      type: 'unhandled_rejection',
      error: event.reason,
      promise: event.promise
    });
  });
  
  // Global error handler
  window.addEventListener('error', (event) => {
    console.error('Global error:', event.error);
    
    reportError({
      type: 'global_error',
      error: event.error,
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno
    });
  });
};

// Call in app initialization
setupGlobalErrorHandling();

// Async error with custom hook
const useAsyncOperation = (asyncFn) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const execute = useCallback(async (...args) => {
    try {
      setLoading(true);
      setError(null);
      const result = await asyncFn(...args);
      setData(result);
      return result;
    } catch (err) {
      setError(err);
      throw err; // Re-throw if caller wants to handle
    } finally {
      setLoading(false);
    }
  }, [asyncFn]);
  
  return { data, error, loading, execute };
};

// Usage
const MyComponent = () => {
  const { data, error, loading, execute } = useAsyncOperation(
    async (id) => {
      const response = await fetch(\`/api/users/\${id}\`);
      if (!response.ok) throw new Error('Fetch failed');
      return response.json();
    }
  );
  
  const handleClick = async () => {
    try {
      await execute(123);
    } catch (err) {
      console.error('Operation failed:', err);
    }
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={loading}>
        Fetch User
      </button>
      {loading && <div>Loading...</div>}
      {error && <div>Error: {error.message}</div>}
      {data && <div>{data.name}</div>}
    </div>
  );
};

// Timeout with error handling
const ComponentWithTimeout = () => {
  const [message, setMessage] = useState('');
  const handleError = useErrorHandler();
  
  useEffect(() => {
    const timerId = setTimeout(() => {
      try {
        // Risky operation
        riskyOperation();
        setMessage('Success');
      } catch (error) {
        handleError(error);
      }
    }, 1000);
    
    return () => clearTimeout(timerId);
  }, [handleError]);
  
  return <div>{message}</div>;
};

// Parallel requests with error handling
const MultipleRequests = () => {
  const [data, setData] = useState(null);
  const [errors, setErrors] = useState([]);
  
  useEffect(() => {
    const fetchAll = async () => {
      const urls = ['/api/data1', '/api/data2', '/api/data3'];
      
      // Promise.allSettled to handle partial failures
      const results = await Promise.allSettled(
        urls.map(url => fetch(url).then(r => r.json()))
      );
      
      const successful = results
        .filter(r => r.status === 'fulfilled')
        .map(r => r.value);
      
      const failed = results
        .filter(r => r.status === 'rejected')
        .map(r => r.reason);
      
      setData(successful);
      setErrors(failed);
    };
    
    fetchAll();
  }, []);
  
  return (
    <div>
      {errors.length > 0 && (
        <div>
          {errors.length} requests failed
        </div>
      )}
      {data && <div>{JSON.stringify(data)}</div>}
    </div>
  );
};
Warning: Always handle promise rejections! Unhandled rejections can cause silent failures. Use try/catch with async/await or .catch() with promises. Set up global handlers as a safety net.

11.5 Error Reporting and Monitoring Integration

Service Features Integration
Sentry Error tracking, performance, releases @sentry/react SDK
LogRocket Session replay, error tracking logrocket + logrocket-react
Bugsnag Error monitoring, releases @bugsnag/js + @bugsnag/plugin-react
Rollbar Real-time error tracking rollbar package

Example: Error monitoring integration

// Sentry integration
import * as Sentry from '@sentry/react';

// Initialize Sentry
Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV,
  release: process.env.REACT_APP_VERSION,
  tracesSampleRate: 1.0, // Capture 100% of transactions
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay()
  ],
  beforeSend(event, hint) {
    // Modify or filter events before sending
    if (event.exception) {
      console.error('Sending error to Sentry:', hint.originalException);
    }
    return event;
  }
});

// Use Sentry's error boundary
const App = () => (
  <Sentry.ErrorBoundary
    fallback={({ error, resetError }) => (
      <div>
        <h1>An error occurred</h1>
        <p>{error.message}</p>
        <button onClick={resetError}>Try again</button>
      </div>
    )}
    showDialog
  >
    <Routes />
  </Sentry.ErrorBoundary>
);

// Manual error capture
const handleError = (error) => {
  Sentry.captureException(error, {
    tags: {
      component: 'UserProfile',
      action: 'fetchUser'
    },
    level: 'error',
    extra: {
      userId: user.id,
      timestamp: Date.now()
    }
  });
};

// Add user context
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username
});

// Add breadcrumbs for debugging
Sentry.addBreadcrumb({
  category: 'user-action',
  message: 'User clicked submit button',
  level: 'info'
});

// LogRocket integration
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

// Initialize LogRocket
LogRocket.init('your-app-id/project-name', {
  release: process.env.REACT_APP_VERSION,
  console: {
    shouldAggregateConsoleErrors: true
  }
});

setupLogRocketReact(LogRocket);

// Identify users
LogRocket.identify(user.id, {
  name: user.name,
  email: user.email,
  subscriptionType: user.plan
});

// LogRocket with Sentry
LogRocket.getSessionURL((sessionURL) => {
  Sentry.configureScope((scope) => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// Custom error reporting service
class ErrorReporter {
  static async report(error, errorInfo, context = {}) {
    const errorData = {
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo?.componentStack,
      context,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: new Date().toISOString(),
      user: this.getUserContext()
    };
    
    try {
      await fetch('/api/errors', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(errorData)
      });
    } catch (reportError) {
      console.error('Failed to report error:', reportError);
    }
  }
  
  static getUserContext() {
    // Get user info from your auth system
    return {
      id: localStorage.getItem('userId'),
      sessionId: localStorage.getItem('sessionId')
    };
  }
}

// Use in error boundary
class MonitoredErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    // Report to multiple services
    ErrorReporter.report(error, errorInfo, {
      component: this.props.name
    });
    
    Sentry.captureException(error, {
      contexts: { react: errorInfo }
    });
    
    LogRocket.captureException(error, {
      extra: errorInfo
    });
  }
  
  render() {
    if (this.state.hasError) {
      return <ErrorFallback />;
    }
    return this.props.children;
  }
}

// Automatic error reporting with fetch interceptor
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  try {
    const response = await originalFetch(...args);
    
    if (!response.ok) {
      Sentry.captureMessage(\`HTTP Error: \${response.status}\`, {
        level: 'warning',
        extra: {
          url: args[0],
          status: response.status
        }
      });
    }
    
    return response;
  } catch (error) {
    Sentry.captureException(error, {
      tags: { type: 'fetch-error' }
    });
    throw error;
  }
};
Note: Choose an error monitoring service that fits your needs. Sentry is popular for error tracking, LogRocket adds session replay, and both provide valuable debugging context. Always sanitize sensitive data before reporting.

11.6 Fallback UI Components and Recovery Patterns

Pattern Description User Experience
Full page fallback Replace entire app with error page Critical errors only
Section fallback Replace section with error message Rest of app still works
Inline error Show error inline in component Minimal disruption
Retry button Let user retry failed operation Self-service recovery
Automatic retry Retry with exponential backoff Seamless recovery attempt
Graceful degradation Show partial content/reduced features Better than nothing

Example: Fallback UI and recovery patterns

// Simple error fallback
const ErrorFallback = ({ error, resetError }) => (
  <div className="error-container">
    <h2>⚠️ Something went wrong</h2>
    <p>{error.message}</p>
    <button onClick={resetError}>Try again</button>
  </div>
);

// Feature-specific fallback
const FeatureFallback = ({ error, resetError, featureName }) => (
  <div className="feature-error">
    <p>Unable to load {featureName}</p>
    <button onClick={resetError}>Retry</button>
    <button onClick={() => window.location.reload()}>
      Reload Page
    </button>
  </div>
);

// Graceful degradation example
const DataList = ({ items }) => {
  const [error, setError] = useState(null);
  const [enhancedData, setEnhancedData] = useState(null);
  
  useEffect(() => {
    // Try to enhance data with additional info
    const enhance = async () => {
      try {
        const enhanced = await fetchEnhancedData(items);
        setEnhancedData(enhanced);
      } catch (err) {
        setError(err);
        // Continue with basic data
      }
    };
    
    enhance();
  }, [items]);
  
  // Show basic data if enhancement fails
  const displayData = enhancedData || items;
  
  return (
    <div>
      {error && (
        <div className="warning">
          Showing basic view (enhanced features unavailable)
        </div>
      )}
      {displayData.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

// Error boundary with reset key
const App = () => {
  const [resetKey, setResetKey] = useState(0);
  
  return (
    <ErrorBoundary
      resetKey={resetKey} // Changes when state updates
      onReset={() => setResetKey(k => k + 1)}
      FallbackComponent={ErrorFallback}
    >
      <Routes />
    </ErrorBoundary>
  );
};

// Automatic retry with exponential backoff
const useRetryableAsync = (asyncFn, maxRetries = 3) => {
  const [state, setState] = useState({
    data: null,
    error: null,
    loading: false,
    retryCount: 0
  });
  
  const execute = useCallback(async (...args) => {
    setState(s => ({ ...s, loading: true, error: null }));
    
    for (let i = 0; i <= maxRetries; i++) {
      try {
        const result = await asyncFn(...args);
        setState({
          data: result,
          error: null,
          loading: false,
          retryCount: i
        });
        return result;
      } catch (error) {
        if (i === maxRetries) {
          setState({
            data: null,
            error,
            loading: false,
            retryCount: i
          });
          throw error;
        }
        
        // Exponential backoff: 1s, 2s, 4s
        const delay = Math.pow(2, i) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }, [asyncFn, maxRetries]);
  
  return { ...state, execute };
};

// Usage
const DataComponent = () => {
  const { data, error, loading, retryCount, execute } = useRetryableAsync(
    async () => {
      const response = await fetch('/api/data');
      if (!response.ok) throw new Error('Fetch failed');
      return response.json();
    }
  );
  
  useEffect(() => {
    execute();
  }, [execute]);
  
  if (loading) return <div>Loading... (attempt {retryCount + 1}/3)</div>;
  if (error) return <div>Failed after {retryCount} retries</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Chunk load error recovery (code splitting)
const ChunkLoadErrorFallback = () => (
  <div className="chunk-error">
    <h2>Update Available</h2>
    <p>A new version of the app is available.</p>
    <button onClick={() => window.location.reload()}>
      Reload to Update
    </button>
  </div>
);

// Network error fallback
const NetworkErrorFallback = ({ resetError }) => (
  <div className="network-error">
    <h2>🌐 Connection Issue</h2>
    <p>Please check your internet connection.</p>
    <button onClick={resetError}>Retry</button>
  </div>
);

// Multiple error boundaries for granular fallbacks
const Dashboard = () => (
  <div>
    <Header /> {/* Outside boundaries - always shown */}
    
    <div className="dashboard-content">
      <ErrorBoundary FallbackComponent={SidebarError}>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary FallbackComponent={MainContentError}>
        <MainContent />
      </ErrorBoundary>
      
      <ErrorBoundary FallbackComponent={WidgetError}>
        <Widgets />
      </ErrorBoundary>
    </div>
  </div>
);

Error Handling Best Practices

12. Refs and DOM Manipulation

12.1 useRef Hook for DOM Element Access

Concept Description Use Case
useRef Hook Creates mutable ref object with .current property that persists across renders DOM access, storing mutable values, keeping previous values
DOM Ref Reference to actual DOM element when assigned via ref attribute Focus management, scroll control, measuring elements
Ref Stability Ref object remains the same across renders, only .current changes Avoid re-renders when storing mutable values
No Re-render Changing ref.current doesn't trigger component re-render Storing timers, intervals, subscriptions

Example: Basic DOM Element Access

import { useRef, useEffect } from 'react';

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

  useEffect(() => {
    // Access DOM element after mount
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  const handleClear = () => {
    if (inputRef.current) {
      inputRef.current.value = '';
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClear}>Clear</button>
    </div>
  );
}

Example: Multiple Refs with Array

function MultipleInputs() {
  const inputRefs = useRef([]);

  const focusInput = (index) => {
    inputRefs.current[index]?.focus();
  };

  return (
    <div>
      {[0, 1, 2].map((index) => (
        <input
          key={index}
          ref={(el) => inputRefs.current[index] = el}
          onKeyPress={(e) => {
            if (e.key === 'Enter' && index < 2) {
              focusInput(index + 1);
            }
          }}
        />
      ))}
    </div>
  );
}

Example: Storing Mutable Values

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  const renderCountRef = useRef(0);

  useEffect(() => {
    // Track render count without causing re-renders
    renderCountRef.current += 1;
  });

  const startTimer = () => {
    if (!intervalRef.current) {
      intervalRef.current = setInterval(() => {
        setCount((c) => c + 1);
      }, 1000);
    }
  };

  const stopTimer = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };

  useEffect(() => {
    return () => stopTimer(); // Cleanup on unmount
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Renders: {renderCountRef.current}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}
Ref Pattern Syntax When to Use
Initial Value useRef(initialValue) Set .current to initial value (useful for non-DOM refs)
Null DOM Ref useRef(null) Standard pattern for DOM element refs
Callback Ref ref={(el) => ...} Need to run code when ref attaches/detaches
Conditional Ref ref={condition ? myRef : null} Conditionally attach ref

12.2 forwardRef and Ref Forwarding Patterns

Concept Description Use Case
forwardRef HOC that allows component to receive and pass ref to child element Custom components that need to expose DOM element
Ref Forwarding Technique to automatically pass ref through component to child Reusable UI components (Input, Button wrappers)
Display Name Set component.displayName for better debugging with forwardRef DevTools shows meaningful names
Generic Refs TypeScript generic type for typed ref forwarding Type-safe ref forwarding in TS

Example: Basic Ref Forwarding

import { forwardRef } from 'react';

// Without forwardRef - Won't work
function BrokenInput(props) {
  return <input {...props} />; // ref prop not forwarded
}

// With forwardRef - Works correctly
const CustomInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});
CustomInput.displayName = 'CustomInput';

// Usage
function Form() {
  const inputRef = useRef(null);

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

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

Example: Styled Component with Ref Forwarding

const FancyButton = forwardRef(({ children, variant = 'primary', ...props }, ref) => {
  const className = `btn btn-${variant}`;
  
  return (
    <button ref={ref} className={className} {...props}>
      {children}
    </button>
  );
});
FancyButton.displayName = 'FancyButton';

// Usage with ref
function App() {
  const btnRef = useRef(null);

  const measureButton = () => {
    if (btnRef.current) {
      const { width, height } = btnRef.current.getBoundingClientRect();
      console.log(`Button size: ${width}x${height}`);
    }
  };

  return (
    <FancyButton ref={btnRef} variant="success" onClick={measureButton}>
      Click Me
    </FancyButton>
  );
}

Example: Combining Own Ref with Forwarded Ref

const InputWithValidation = forwardRef(({ onValidate, ...props }, forwardedRef) => {
  const internalRef = useRef(null);

  // Combine both refs
  useEffect(() => {
    if (forwardedRef) {
      if (typeof forwardedRef === 'function') {
        forwardedRef(internalRef.current);
      } else {
        forwardedRef.current = internalRef.current;
      }
    }
  }, [forwardedRef]);

  const handleBlur = () => {
    if (internalRef.current && onValidate) {
      onValidate(internalRef.current.value);
    }
  };

  return (
    <input
      ref={internalRef}
      onBlur={handleBlur}
      {...props}
    />
  );
});
InputWithValidation.displayName = 'InputWithValidation';

Example: TypeScript Ref Forwarding

import { forwardRef, Ref } from 'react';

interface InputProps {
  label: string;
  error?: string;
}

const FormInput = forwardRef<HTMLInputElement, InputProps>(
  ({ label, error, ...props }, ref) => {
    return (
      <div className="form-group">
        <label>{label}</label>
        <input ref={ref} {...props} />
        {error && <span className="error">{error}</span>}
      </div>
    );
  }
);
FormInput.displayName = 'FormInput';

// Usage with type safety
function TypedForm() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  return <FormInput ref={inputRef} label="Email" />;
}

Ref Forwarding Best Practices:

  • Always set displayName for better DevTools experience
  • Use forwardRef for reusable UI components
  • Document which DOM element the ref points to
  • Consider using useImperativeHandle for custom APIs
  • Handle both function and object refs in custom logic

12.3 useImperativeHandle for Custom Ref APIs

Concept Description Use Case
useImperativeHandle Customizes ref instance value exposed to parent components Expose limited, controlled API instead of full DOM element
Ref API Custom object with methods/properties exposed via ref Hide implementation, provide semantic methods
Encapsulation Hide internal DOM structure, expose only intended interface Complex components with internal state/refs
Dependencies Third parameter array, similar to useEffect Recreate handle when dependencies change

Example: Custom Input with Imperative Methods

import { forwardRef, useRef, useImperativeHandle } from 'react';

const AdvancedInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const [value, setValue] = useState('');

  // Expose custom API instead of raw DOM element
  useImperativeHandle(ref, () => ({
    // Custom methods
    focus: () => {
      inputRef.current?.focus();
    },
    clear: () => {
      setValue('');
      inputRef.current?.focus();
    },
    getValue: () => value,
    setValue: (newValue) => setValue(newValue),
    // Expose select DOM methods
    scrollIntoView: () => {
      inputRef.current?.scrollIntoView({ behavior: 'smooth' });
    }
  }), [value]); // Recreate when value changes

  return (
    <input
      ref={inputRef}
      value={value}
      onChange={(e) => setValue(e.target.value)}
      {...props}
    />
  );
});

// Usage
function Form() {
  const inputRef = useRef(null);

  const handleSubmit = () => {
    const value = inputRef.current?.getValue();
    console.log('Value:', value);
    inputRef.current?.clear();
  };

  return (
    <div>
      <AdvancedInput ref={inputRef} />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}

Example: Modal Component with Imperative API

const Modal = forwardRef(({ children }, ref) => {
  const [isOpen, setIsOpen] = useState(false);
  const [content, setContent] = useState(null);
  const modalRef = useRef(null);

  useImperativeHandle(ref, () => ({
    open: (newContent) => {
      setContent(newContent);
      setIsOpen(true);
    },
    close: () => {
      setIsOpen(false);
    },
    toggle: () => {
      setIsOpen((prev) => !prev);
    },
    isOpen: () => isOpen,
    updateContent: (newContent) => {
      setContent(newContent);
    }
  }), [isOpen]);

  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={() => setIsOpen(false)}>
      <div
        ref={modalRef}
        className="modal-content"
        onClick={(e) => e.stopPropagation()}
      >
        {content || children}
      </div>
    </div>
  );
});

// Usage
function App() {
  const modalRef = useRef(null);

  const showSuccess = () => {
    modalRef.current?.open(<div>Success!</div>);
    setTimeout(() => modalRef.current?.close(), 2000);
  };

  return (
    <div>
      <button onClick={showSuccess}>Show Modal</button>
      <Modal ref={modalRef} />
    </div>
  );
}

Example: Video Player with Imperative Controls

const VideoPlayer = forwardRef(({ src }, ref) => {
  const videoRef = useRef(null);

  useImperativeHandle(ref, () => ({
    play: () => videoRef.current?.play(),
    pause: () => videoRef.current?.pause(),
    stop: () => {
      if (videoRef.current) {
        videoRef.current.pause();
        videoRef.current.currentTime = 0;
      }
    },
    seek: (time) => {
      if (videoRef.current) {
        videoRef.current.currentTime = time;
      }
    },
    setVolume: (volume) => {
      if (videoRef.current) {
        videoRef.current.volume = Math.max(0, Math.min(1, volume));
      }
    },
    getCurrentTime: () => videoRef.current?.currentTime ?? 0,
    getDuration: () => videoRef.current?.duration ?? 0,
    isPlaying: () => !videoRef.current?.paused
  }));

  return <video ref={videoRef} src={src} />;
});

// Usage
function VideoControls() {
  const playerRef = useRef(null);

  const handleSkip = (seconds) => {
    const current = playerRef.current?.getCurrentTime() ?? 0;
    playerRef.current?.seek(current + seconds);
  };

  return (
    <div>
      <VideoPlayer ref={playerRef} src="video.mp4" />
      <button onClick={() => playerRef.current?.play()}>Play</button>
      <button onClick={() => playerRef.current?.pause()}>Pause</button>
      <button onClick={() => handleSkip(-10)}>-10s</button>
      <button onClick={() => handleSkip(10)}>+10s</button>
    </div>
  );
}
Pattern Description Example
Action Methods Methods that perform actions (focus, clear, submit) { focus: () => {...}, clear: () => {...} }
Query Methods Methods that return values (getValue, isValid) { getValue: () => value, isValid: () => valid }
State Methods Methods that change internal state (open, close) { open: () => {...}, close: () => {...} }
Controlled API Expose subset of DOM methods with validation { play: () => video.play(), pause: () => video.pause() }

useImperativeHandle Caveats:

  • Use sparingly - prefer controlled components and props
  • Don't expose full DOM element unless necessary
  • Document the imperative API thoroughly
  • Consider dependencies array to avoid stale closures
  • Overuse breaks React's declarative paradigm

12.4 Focus Management and Accessibility

Technique Description Accessibility Impact
Auto Focus Automatically focus element on mount or condition Improves keyboard navigation, required for modals
Focus Trap Keep focus within container (modal, dropdown) Prevents focus from escaping modal dialogs
Focus Return Return focus to trigger element after modal closes Maintains user's place in navigation flow
Skip Links Allow keyboard users to skip navigation WCAG 2.1 Level A requirement
Focus Indicators Visible outline/highlight for focused elements Required for keyboard-only users
Tab Order Control tabIndex for logical navigation Ensures logical focus flow

Example: Auto Focus on Mount

function LoginForm() {
  const usernameRef = useRef(null);

  useEffect(() => {
    // Focus first input on mount
    usernameRef.current?.focus();
  }, []);

  return (
    <form>
      <input ref={usernameRef} name="username" placeholder="Username" />
      <input name="password" type="password" placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  );
}

Example: Focus Trap in Modal

function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);
  const previousFocusRef = useRef(null);

  useEffect(() => {
    if (isOpen) {
      // Save current focus
      previousFocusRef.current = document.activeElement;

      // Focus modal
      modalRef.current?.focus();

      // Setup focus trap
      const handleKeyDown = (e) => {
        if (e.key === 'Tab') {
          const focusableElements = modalRef.current?.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
          );
          
          if (!focusableElements?.length) return;

          const firstElement = focusableElements[0];
          const lastElement = focusableElements[focusableElements.length - 1];

          if (e.shiftKey) {
            // Shift+Tab
            if (document.activeElement === firstElement) {
              lastElement.focus();
              e.preventDefault();
            }
          } else {
            // Tab
            if (document.activeElement === lastElement) {
              firstElement.focus();
              e.preventDefault();
            }
          }
        }

        // Close on Escape
        if (e.key === 'Escape') {
          onClose();
        }
      };

      document.addEventListener('keydown', handleKeyDown);
      return () => document.removeEventListener('keydown', handleKeyDown);
    } else {
      // Return focus when modal closes
      previousFocusRef.current?.focus();
    }
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return (
    <div className="modal-overlay">
      <div
        ref={modalRef}
        className="modal"
        role="dialog"
        aria-modal="true"
        tabIndex={-1}
      >
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}
function PageLayout({ children }) {
  const mainContentRef = useRef(null);

  const skipToMain = (e) => {
    e.preventDefault();
    mainContentRef.current?.focus();
    mainContentRef.current?.scrollIntoView();
  };

  return (
    <>
      <a
        href="#main"
        className="skip-link"
        onClick={skipToMain}
        style={{
          position: 'absolute',
          left: '-9999px',
          zIndex: 999
        }}
        onFocus={(e) => {
          e.target.style.left = '0';
        }}
        onBlur={(e) => {
          e.target.style.left = '-9999px';
        }}
      >
        Skip to main content
      </a>
      
      <nav>
        {/* Navigation items */}
      </nav>
      
      <main
        ref={mainContentRef}
        id="main"
        tabIndex={-1}
        style={{ outline: 'none' }}
      >
        {children}
      </main>
    </>
  );
}

Example: Focus Management in Dynamic Lists

function TodoList() {
  const [todos, setTodos] = useState([]);
  const itemRefs = useRef(new Map());

  const deleteTodo = (id, index) => {
    setTodos((prev) => prev.filter((t) => t.id !== id));

    // Focus next item or previous if last
    setTimeout(() => {
      const nextIndex = Math.min(index, todos.length - 2);
      const nextId = todos[nextIndex]?.id;
      
      if (nextId) {
        itemRefs.current.get(nextId)?.focus();
      }
    }, 0);
  };

  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={todo.id}>
          <span>{todo.text}</span>
          <button
            ref={(el) => {
              if (el) {
                itemRefs.current.set(todo.id, el);
              } else {
                itemRefs.current.delete(todo.id);
              }
            }}
            onClick={() => deleteTodo(todo.id, index)}
          >
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}
ARIA Attribute Purpose Usage with Refs
aria-activedescendant Identifies focused element in composite widget Update with ref when selection changes
aria-describedby References element describing current element Link input to error message via ref IDs
aria-labelledby References element(s) labeling current element Associate labels with inputs using ref IDs
aria-live Announces dynamic content changes Focus live region ref for announcements

Focus Management Best Practices:

  • Always manage focus when showing/hiding modals
  • Return focus to trigger element after dialog closes
  • Implement focus trap for modal dialogs
  • Provide visible focus indicators (never use outline: none without replacement)
  • Test with keyboard-only navigation
  • Use semantic HTML elements for better focus behavior
  • Consider screen reader announcements with aria-live
  • Avoid unnecessary tabIndex values (rely on natural tab order)

12.5 DOM Measurements and Element Properties

Measurement Property/Method Use Case
Element Size getBoundingClientRect() Get size and position relative to viewport
Scroll Position scrollTop, scrollLeft Track/control scroll position
Offset Dimensions offsetWidth, offsetHeight Element dimensions including padding/border
Client Dimensions clientWidth, clientHeight Element dimensions excluding scrollbars
Scroll Dimensions scrollWidth, scrollHeight Total scrollable content size
Computed Styles getComputedStyle() Get actual CSS values applied

Example: Measuring Element Dimensions

function ElementMeasurements() {
  const elementRef = useRef(null);
  const [dimensions, setDimensions] = useState({});

  useEffect(() => {
    const measureElement = () => {
      if (elementRef.current) {
        const rect = elementRef.current.getBoundingClientRect();
        const computedStyle = window.getComputedStyle(elementRef.current);
        
        setDimensions({
          // Position
          top: rect.top,
          left: rect.left,
          right: rect.right,
          bottom: rect.bottom,
          
          // Size
          width: rect.width,
          height: rect.height,
          
          // Offset dimensions (includes padding, border)
          offsetWidth: elementRef.current.offsetWidth,
          offsetHeight: elementRef.current.offsetHeight,
          
          // Client dimensions (excludes scrollbar)
          clientWidth: elementRef.current.clientWidth,
          clientHeight: elementRef.current.clientHeight,
          
          // Scroll dimensions
          scrollWidth: elementRef.current.scrollWidth,
          scrollHeight: elementRef.current.scrollHeight,
          
          // Computed styles
          marginTop: computedStyle.marginTop,
          paddingLeft: computedStyle.paddingLeft,
        });
      }
    };

    measureElement();
    
    // Remeasure on resize
    window.addEventListener('resize', measureElement);
    return () => window.removeEventListener('resize', measureElement);
  }, []);

  return (
    <div>
      <div ref={elementRef} style={{ padding: '20px', border: '2px solid' }}>
        Measure me!
      </div>
      <pre>{JSON.stringify(dimensions, null, 2)}</pre>
    </div>
  );
}

Example: Scroll Position Tracking

function ScrollTracker() {
  const containerRef = useRef(null);
  const [scrollInfo, setScrollInfo] = useState({
    scrollTop: 0,
    scrollPercentage: 0,
    isAtBottom: false
  });

  const handleScroll = () => {
    if (containerRef.current) {
      const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
      const scrollPercentage = (scrollTop / (scrollHeight - clientHeight)) * 100;
      const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;

      setScrollInfo({
        scrollTop,
        scrollPercentage: Math.round(scrollPercentage),
        isAtBottom
      });
    }
  };

  const scrollToBottom = () => {
    if (containerRef.current) {
      containerRef.current.scrollTop = containerRef.current.scrollHeight;
    }
  };

  return (
    <div>
      <div>
        <p>Scroll: {scrollInfo.scrollTop}px</p>
        <p>Progress: {scrollInfo.scrollPercentage}%</p>
        <p>At bottom: {scrollInfo.isAtBottom ? 'Yes' : 'No'}</p>
      </div>
      
      <div
        ref={containerRef}
        onScroll={handleScroll}
        style={{ height: '300px', overflow: 'auto' }}
      >
        {/* Long content */}
      </div>
      
      <button onClick={scrollToBottom}>Scroll to Bottom</button>
    </div>
  );
}

Example: Intersection Observer with Refs

function LazyImage({ src, alt }) {
  const imgRef = useRef(null);
  const [isVisible, setIsVisible] = useState(false);
  const [hasLoaded, setHasLoaded] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      {
        rootMargin: '50px', // Load slightly before visible
        threshold: 0.01
      }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef} style={{ minHeight: '200px' }}>
      {isVisible && (
        <img
          src={src}
          alt={alt}
          onLoad={() => setHasLoaded(true)}
          style={{ opacity: hasLoaded ? 1 : 0, transition: 'opacity 0.3s' }}
        />
      )}
    </div>
  );
}

Example: ResizeObserver for Responsive Components

function ResponsiveCard({ children }) {
  const cardRef = useRef(null);
  const [size, setSize] = useState('medium');

  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const width = entry.contentRect.width;
        
        if (width < 300) {
          setSize('small');
        } else if (width < 600) {
          setSize('medium');
        } else {
          setSize('large');
        }
      }
    });

    if (cardRef.current) {
      observer.observe(cardRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={cardRef} className={`card card-${size}`}>
      {children}
    </div>
  );
}
Observer API Purpose Callback Timing
IntersectionObserver Detect when element enters/exits viewport When visibility changes
ResizeObserver Detect when element size changes When dimensions change
MutationObserver Detect DOM tree changes When DOM mutates
PerformanceObserver Monitor performance metrics When performance entries recorded

Performance Considerations:

  • Use IntersectionObserver instead of scroll event listeners
  • Use ResizeObserver instead of window resize + polling
  • Debounce/throttle measurements on scroll/resize when necessary
  • Read measurements in batches to avoid layout thrashing
  • Cache measurements when possible (recompute only on dependency change)
  • Disconnect observers when component unmounts

12.6 Third-party Library Integration with Refs

Library Type Integration Pattern Common Examples
DOM Libraries Pass ref.current to library initialization D3.js, Chart.js, Three.js
jQuery Plugins Initialize plugin on ref element in useEffect DataTables, Select2, jQuery UI
Maps Provide container ref for map initialization Leaflet, Google Maps, Mapbox
Rich Text Editors Mount editor instance to ref element TinyMCE, Quill, Monaco
Video Players Initialize player with ref element Video.js, Plyr, JW Player
Canvas Libraries Get canvas element via ref for rendering Fabric.js, Konva.js, Paper.js

Example: D3.js Chart Integration

import { useRef, useEffect } from 'react';
import * as d3 from 'd3';

function D3Chart({ data }) {
  const svgRef = useRef(null);

  useEffect(() => {
    if (!svgRef.current || !data) return;

    // Clear previous content
    d3.select(svgRef.current).selectAll('*').remove();

    // Create chart
    const svg = d3.select(svgRef.current);
    const width = 500;
    const height = 300;
    const margin = { top: 20, right: 20, bottom: 30, left: 40 };

    const x = d3.scaleBand()
      .domain(data.map(d => d.label))
      .range([margin.left, width - margin.right])
      .padding(0.1);

    const y = d3.scaleLinear()
      .domain([0, d3.max(data, d => d.value)])
      .nice()
      .range([height - margin.bottom, margin.top]);

    // Add bars
    svg.selectAll('rect')
      .data(data)
      .join('rect')
      .attr('x', d => x(d.label))
      .attr('y', d => y(d.value))
      .attr('width', x.bandwidth())
      .attr('height', d => y(0) - y(d.value))
      .attr('fill', 'steelblue');

    // Add axes
    svg.append('g')
      .attr('transform', `translate(0,${height - margin.bottom})`)
      .call(d3.axisBottom(x));

    svg.append('g')
      .attr('transform', `translate(${margin.left},0)`)
      .call(d3.axisLeft(y));

  }, [data]);

  return <svg ref={svgRef} width={500} height={300} />;
}

Example: Leaflet Map Integration

import { useRef, useEffect } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';

function LeafletMap({ center, zoom, markers }) {
  const mapRef = useRef(null);
  const mapInstanceRef = useRef(null);

  useEffect(() => {
    if (!mapRef.current) return;

    // Initialize map
    if (!mapInstanceRef.current) {
      mapInstanceRef.current = L.map(mapRef.current).setView(center, zoom);

      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors'
      }).addTo(mapInstanceRef.current);
    }

    // Cleanup
    return () => {
      if (mapInstanceRef.current) {
        mapInstanceRef.current.remove();
        mapInstanceRef.current = null;
      }
    };
  }, []);

  // Update markers
  useEffect(() => {
    if (!mapInstanceRef.current) return;

    // Clear existing markers
    mapInstanceRef.current.eachLayer((layer) => {
      if (layer instanceof L.Marker) {
        mapInstanceRef.current.removeLayer(layer);
      }
    });

    // Add new markers
    markers?.forEach((marker) => {
      L.marker(marker.position)
        .addTo(mapInstanceRef.current)
        .bindPopup(marker.popup);
    });
  }, [markers]);

  return <div ref={mapRef} style={{ height: '400px', width: '100%' }} />;
}

Example: Monaco Editor Integration

import { useRef, useEffect } from 'react';
import * as monaco from 'monaco-editor';

function CodeEditor({ value, language, onChange }) {
  const editorRef = useRef(null);
  const editorInstanceRef = useRef(null);

  useEffect(() => {
    if (!editorRef.current) return;

    // Create editor
    editorInstanceRef.current = monaco.editor.create(editorRef.current, {
      value: value || '',
      language: language || 'javascript',
      theme: 'vs-dark',
      automaticLayout: true,
      minimap: { enabled: false }
    });

    // Listen to changes
    const disposable = editorInstanceRef.current.onDidChangeModelContent(() => {
      const newValue = editorInstanceRef.current.getValue();
      onChange?.(newValue);
    });

    // Cleanup
    return () => {
      disposable.dispose();
      editorInstanceRef.current?.dispose();
    };
  }, []);

  // Update value from prop
  useEffect(() => {
    if (editorInstanceRef.current && value !== undefined) {
      const currentValue = editorInstanceRef.current.getValue();
      if (currentValue !== value) {
        editorInstanceRef.current.setValue(value);
      }
    }
  }, [value]);

  // Update language
  useEffect(() => {
    if (editorInstanceRef.current && language) {
      const model = editorInstanceRef.current.getModel();
      monaco.editor.setModelLanguage(model, language);
    }
  }, [language]);

  return <div ref={editorRef} style={{ height: '500px', width: '100%' }} />;
}

Example: jQuery Plugin Wrapper

import { useRef, useEffect } from 'react';
import $ from 'jquery';
import 'select2'; // jQuery plugin
import 'select2/dist/css/select2.css';

function Select2Component({ options, value, onChange }) {
  const selectRef = useRef(null);

  useEffect(() => {
    if (!selectRef.current) return;

    // Initialize Select2
    const $select = $(selectRef.current);
    $select.select2({
      data: options,
      width: '100%'
    });

    // Listen to changes
    $select.on('change', (e) => {
      onChange?.(e.target.value);
    });

    // Cleanup
    return () => {
      $select.select2('destroy');
      $select.off('change');
    };
  }, [options]);

  // Update value from prop
  useEffect(() => {
    if (selectRef.current && value !== undefined) {
      $(selectRef.current).val(value).trigger('change');
    }
  }, [value]);

  return (
    <select ref={selectRef}>
      {options.map((opt) => (
        <option key={opt.id} value={opt.id}>
          {opt.text}
        </option>
      ))}
    </select>
  );
}
Integration Challenge Solution Example
Library expects DOM element Pass ref.current in useEffect after mount D3.js, Leaflet initialization
Library manages own state Sync library state with React state via callbacks Editor onChange, map move events
Library mutates DOM Don't render React children in library container Keep library container div empty
Cleanup required Call library destroy/dispose in useEffect cleanup Monaco dispose(), Select2 destroy()
Props update Update library instance in separate useEffect Update map center, chart data
Instance methods needed Store instance in ref, expose via useImperativeHandle Map pan/zoom, editor formatting

Third-party Integration Best Practices:

  • Initialize library in useEffect with empty deps ([] = mount only)
  • Store library instance in ref for method calls
  • Always cleanup (destroy/dispose) in useEffect return
  • Sync React state to library in separate useEffect with deps
  • Don't let React and library both manage same DOM (choose one)
  • Wrap in custom component to encapsulate integration logic
  • Document which React patterns to avoid with library
  • Consider using React wrapper libraries when available

Refs and DOM Manipulation Best Practices

13. Advanced Component Patterns

13.1 Higher-Order Components (HOC) Patterns

HOC Pattern Syntax Description Use Case
Basic HOC withFeature(Component) Function that takes component and returns enhanced component Add props, wrap with context, inject dependencies
HOC with Arguments withFeature(config)(Comp) Curried HOC that accepts configuration Configurable behavior, conditional enhancement
Props Proxy props => <Comp {...props}/> HOC manipulates props before passing to wrapped component Transform, filter, or add props
Inheritance Inversion class extends WrappedComp HOC extends wrapped component class DEPRECATED Modify lifecycle, render - avoid, use hooks instead
Composing HOCs compose(hoc1, hoc2)(Comp) Apply multiple HOCs in sequence Combine multiple enhancements cleanly
Display Name HOC.displayName = 'with...' Set display name for debugging Better DevTools, error messages, stack traces

Example: Basic HOC with authentication

// Basic HOC pattern
function withAuth(Component) {
  return function AuthComponent(props) {
    const { user, loading } = useAuth();
    
    if (loading) return <LoadingSpinner />;
    if (!user) return <Navigate to="/login" />;
    
    return <Component {...props} user={user} />;
  };
}

// Usage
const ProtectedDashboard = withAuth(Dashboard);

// HOC with configuration
function withPermission(permission) {
  return function (Component) {
    return function PermissionComponent(props) {
      const { user } = useAuth();
      
      if (!user?.permissions.includes(permission)) {
        return <AccessDenied />;
      }
      
      return <Component {...props} />;
    };
  };
}

// Usage with config
const AdminPanel = withPermission('admin')(Panel);

Example: Props transformation and composition

// Props transformation HOC
function withUppercase(Component) {
  return function UppercaseComponent({ name, ...props }) {
    return <Component {...props} name={name?.toUpperCase()} />;
  };
}

// Data fetching HOC
function withData(fetchFn, propName) {
  return function (Component) {
    return function DataComponent(props) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      
      useEffect(() => {
        fetchFn().then((result) => {
          setData(result);
          setLoading(false);
        });
      }, []);
      
      return <Component {...props} {...{[propName]: data}} loading={loading} />;
    };
  };
}

// Compose multiple HOCs
import { compose } from 'redux'; // or lodash/fp

const enhance = compose(
  withAuth,
  withData(fetchUserProfile, 'profile'),
  withUppercase
);

const EnhancedProfile = enhance(ProfileComponent);
HOC Best Practices: Use descriptive names (with* prefix), pass all props through, set displayName for debugging, prefer hooks for most cases, compose HOCs with utility functions, avoid mutating wrapped component.

13.2 Render Props and Function-as-Children

Pattern Syntax Description Use Case
Render Prop <Comp render={data => ...}/> Pass function as prop that returns React element Share logic while giving render control to consumer
Children as Function <Comp>{data => ...}</Comp> Children prop is a function receiving data More ergonomic API, follows React conventions
Multiple Render Props header={...} footer={...} Multiple function props for different render areas Flexible layout with pluggable sections
Render Props with Hooks useData().render() Hooks replaced most render prop use cases PREFERRED Modern alternative - cleaner, more composable

Example: Mouse tracker with render prop

// Render prop component
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);
  
  return render(position);
}

// Usage with render prop
<MouseTracker 
  render={({ x, y }) => (
    <h1>Mouse at {x}, {y}</h1>
  )}
/>

// Children as function pattern
function DataProvider({ url, children }) {
  const [data, loading, error] = useFetch(url);
  return children({ data, loading, error });
}

// Usage
<DataProvider url="/api/users">
  {({ data, loading, error }) => {
    if (loading) return <Spinner />;
    if (error) return <Error message={error} />;
    return <UserList users={data} />;
  }}
</DataProvider>

Example: Modern hook alternative (preferred)

// Custom hook replaces render prop pattern
function useMouse() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);
  
  return position;
}

// Usage - much cleaner!
function MyComponent() {
  const { x, y } = useMouse();
  return <h1>Mouse at {x}, {y}</h1>;
}
Warning: Render props can hurt performance if function is recreated on each render. Use useCallback for render prop functions. Consider custom hooks as modern alternative.

13.3 Compound Components and Component APIs

Pattern Syntax Description Use Case
Compound Components Menu.Item, Menu.Divider Components designed to work together, share implicit state Complex UI with coordinated subcomponents
Static Properties Component.SubComponent Attach child components as static properties of parent Namespace related components, cleaner imports
Context-based Context.Provider + useContext Share state via context between parent and children Flexible structure, works at any nesting level
Clone Element React.cloneElement(child) Clone and inject props into children Add props to unknown children, less flexible
Controlled Compound activeItem={...} onChange Parent controls shared state externally Integrate with forms, external state management

Example: Tabs compound component with context

// Context for shared state
const TabContext = createContext();

function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <TabContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list" role="tablist">{children}</div>;
}

function Tab({ id, children }) {
  const { activeTab, setActiveTab } = useContext(TabContext);
  const isActive = activeTab === id;
  
  return (
    <button
      role="tab"
      aria-selected={isActive}
      onClick={() => setActiveTab(id)}
      className={isActive ? 'active' : ''}
    >
      {children}
    </button>
  );
}

function TabPanel({ id, children }) {
  const { activeTab } = useContext(TabContext);
  if (activeTab !== id) return null;
  
  return <div role="tabpanel">{children}</div>;
}

// Attach as static properties
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

// Usage - declarative API
<Tabs defaultTab="home">
  <Tabs.List>
    <Tabs.Tab id="home">Home</Tabs.Tab>
    <Tabs.Tab id="profile">Profile</Tabs.Tab>
    <Tabs.Tab id="settings">Settings</Tabs.Tab>
  </Tabs.List>
  
  <Tabs.Panel id="home">Home content</Tabs.Panel>
  <Tabs.Panel id="profile">Profile content</Tabs.Panel>
  <Tabs.Panel id="settings">Settings content</Tabs.Panel>
</Tabs>

Example: Controlled compound component

// Controlled version for external state control
function ControlledTabs({ children, activeTab, onTabChange }) {
  return (
    <TabContext.Provider value={{ activeTab, setActiveTab: onTabChange }}>
      <div className="tabs">{children}</div>
    </TabContext.Provider>
  );
}

// Usage with external state
function App() {
  const [tab, setTab] = useState('home');
  
  return (
    <>
      <button onClick={() => setTab('profile')}>
        Go to Profile (external)
      </button>
      
      <ControlledTabs activeTab={tab} onTabChange={setTab}>
        <Tabs.List>
          <Tabs.Tab id="home">Home</Tabs.Tab>
          <Tabs.Tab id="profile">Profile</Tabs.Tab>
        </Tabs.List>
        <Tabs.Panel id="home">Home</Tabs.Panel>
        <Tabs.Panel id="profile">Profile</Tabs.Panel>
      </ControlledTabs>
    </>
  );
}
Compound Component Benefits: Flexible, declarative API; implicit state sharing; easy to extend; semantic JSX structure; reduced prop drilling; better separation of concerns.

13.4 Custom Hooks and Logic Reuse Patterns

Hook Pattern Purpose Returns Use Case
State Hook Encapsulate stateful logic [state, setState] Toggle, counter, form state, modal open/close
Effect Hook Side effects and subscriptions void or cleanup Event listeners, timers, subscriptions, polling
Data Hook Data fetching and caching { data, loading, error } API calls, async data, pagination, infinite scroll
Computation Hook Derived state and calculations computedValue Filtering, sorting, validation, formatting
Ref Hook DOM access, stable refs ref Focus management, measurements, previous values
Composite Hook Combine multiple hooks { ...multiple values } Complex workflows, coordinated state

Example: Custom hooks for common patterns

// Toggle hook
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => setValue(v => !v), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);
  
  return [value, toggle, setTrue, setFalse];
}

// Usage
const [isOpen, toggle, open, close] = useToggle();

// Local storage hook
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });
  
  const setValue = useCallback((value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error('Error saving to localStorage:', error);
    }
  }, [key, storedValue]);
  
  return [storedValue, setValue];
}

// Previous value hook
function usePrevious(value) {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  }, [value]);
  
  return ref.current;
}

Example: Data fetching and debounce hooks

// Fetch hook with loading/error states
function useFetch(url, options) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let isCancelled = false;
    
    setLoading(true);
    fetch(url, options)
      .then(res => res.json())
      .then(data => {
        if (!isCancelled) {
          setData(data);
          setError(null);
        }
      })
      .catch(err => {
        if (!isCancelled) {
          setError(err.message);
          setData(null);
        }
      })
      .finally(() => {
        if (!isCancelled) setLoading(false);
      });
    
    return () => { isCancelled = true; };
  }, [url, JSON.stringify(options)]);
  
  return { data, loading, error };
}

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

// Usage - search with debounce
function SearchComponent() {
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 500);
  const { data, loading } = useFetch(`/api/search?q=${debouncedSearch}`);
  
  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      {loading ? <Spinner /> : <Results data={data} />}
    </div>
  );
}
Custom Hook Guidelines: Always start with 'use' prefix, follow hooks rules, return values/functions consumers need, use useCallback for returned functions, document parameters and return values, handle cleanup in useEffect.

13.5 Polymorphic Components and Generic Props

Pattern Implementation Description Use Case
As Prop Pattern <Box as="button"> Component renders as different HTML elements Flexible primitive components, design systems
Component Prop <Comp component={Link}> Accept component as prop to customize rendering Wrapper components, list items, custom elements
Render Function renderItem={(item) => ...} Function prop that returns rendered content Lists, grids, custom rendering logic
TypeScript Generic <T extends ElementType> Type-safe polymorphic components with TS TS Fully typed props based on element type

Example: Polymorphic Box component

// Basic polymorphic component
function Box({ as: Component = 'div', children, ...props }) {
  return <Component {...props}>{children}</Component>;
}

// Usage - renders as different elements
<Box>Default div</Box>
<Box as="section">Section element</Box>
<Box as="button" onClick={handleClick}>Button element</Box>
<Box as={Link} to="/home">React Router Link</Box>

// TypeScript polymorphic component with full type safety
import { ElementType, ComponentPropsWithoutRef } from 'react';

type PolymorphicProps<T extends ElementType> = {
  as?: T;
  children?: React.ReactNode;
} & ComponentPropsWithoutRef<T>;

function PolymorphicBox<T extends ElementType = 'div'>({
  as,
  children,
  ...props
}: PolymorphicProps<T>) {
  const Component = as || 'div';
  return <Component {...props}>{children}</Component>;
}

// TypeScript enforces correct props based on 'as' prop
<PolymorphicBox as="a" href="/path">Link</PolymorphicBox> // ✓ href allowed
<PolymorphicBox as="button" onClick={fn}>Button</PolymorphicBox> // ✓ onClick allowed
<PolymorphicBox as="div" href="/path">Div</PolymorphicBox> // ✗ TS error - href not valid

Example: Generic list component

// Generic list with custom rendering
function List({ items, renderItem, emptyMessage = 'No items' }) {
  if (!items?.length) {
    return <div className="empty">{emptyMessage}</div>;
  }
  
  return (
    <ul className="list">
      {items.map((item, index) => (
        <li key={item.id || index}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// Usage with different render functions
<List 
  items={users} 
  renderItem={(user) => <UserCard user={user} />}
/>

<List 
  items={products} 
  renderItem={(product, idx) => (
    <div>
      {idx + 1}. {product.name} - ${product.price}
    </div>
  )}
/>

// With component prop pattern
function Container({ component: Component = 'div', children, ...props }) {
  return (
    <Component className="container" {...props}>
      {children}
    </Component>
  );
}

// Usage
<Container>Default div container</Container>
<Container component="section">Section container</Container>
<Container component={Card}>Custom component</Container>
Polymorphic Component Benefits: Single component, multiple elements; reduces component proliferation; type-safe with TypeScript; flexible for design systems; maintains consistent styling/behavior.

13.6 Component Composition vs Inheritance

Approach React Recommendation Pattern Reason
Composition PREFERRED Strongly recommended Children prop, props, slots More flexible, easier to understand, React way
Inheritance AVOID Not recommended Class extends Component Tight coupling, less flexible, not idiomatic React
Containment Use children prop <Box>{children}</Box> Generic containers, wrappers, layouts
Specialization Compose with props <Dialog type="alert"> Specific variants from generic components
Named Slots Multiple props header={...} footer={...} Complex layouts with multiple sections

Example: Composition patterns (preferred)

// Containment - generic container with children
function Dialog({ title, children, footer }) {
  return (
    <div className="dialog">
      <div className="dialog-header">
        <h2>{title}</h2>
      </div>
      <div className="dialog-body">
        {children}
      </div>
      {footer && (
        <div className="dialog-footer">
          {footer}
        </div>
      )}
    </div>
  );
}

// Specialization - specific dialog variants via composition
function WelcomeDialog() {
  return (
    <Dialog 
      title="Welcome"
      footer={
        <button onClick={handleStart}>Get Started</button>
      }
    >
      <p>Thank you for joining!</p>
    </Dialog>
  );
}

function ConfirmDialog({ onConfirm, onCancel }) {
  return (
    <Dialog 
      title="Confirm Action"
      footer={
        <>
          <button onClick={onCancel}>Cancel</button>
          <button onClick={onConfirm}>Confirm</button>
        </>
      }
    >
      <p>Are you sure you want to proceed?</p>
    </Dialog>
  );
}

// Named slots pattern
function Layout({ header, sidebar, main, footer }) {
  return (
    <div className="layout">
      <header>{header}</header>
      <div className="layout-body">
        <aside>{sidebar}</aside>
        <main>{main}</main>
      </div>
      <footer>{footer}</footer>
    </div>
  );
}

// Usage
<Layout
  header={<Header />}
  sidebar={<Sidebar />}
  main={<MainContent />}
  footer={<Footer />}
/>

Example: Why composition beats inheritance

// ❌ Inheritance approach (avoid)
class BaseDialog extends Component {
  render() {
    return (
      <div className="dialog">
        <h2>{this.props.title}</h2>
        {this.renderContent()}
        {this.renderActions()}
      </div>
    );
  }
}

class WelcomeDialog extends BaseDialog {
  renderContent() {
    return <p>Welcome!</p>;
  }
  renderActions() {
    return <button>Get Started</button>;
  }
}
// Issues: Tight coupling, hard to modify, fragile base class

// ✓ Composition approach (preferred)
function Dialog({ title, children, actions }) {
  return (
    <div className="dialog">
      <h2>{title}</h2>
      <div>{children}</div>
      <div>{actions}</div>
    </div>
  );
}

function WelcomeDialog() {
  return (
    <Dialog 
      title="Welcome"
      actions={<button>Get Started</button>}
    >
      <p>Welcome!</p>
    </Dialog>
  );
}
// Benefits: Flexible, clear, easy to modify, testable

// Composition with hooks for behavior reuse
function useDialogState() {
  const [isOpen, setIsOpen] = useState(false);
  return { isOpen, open: () => setIsOpen(true), close: () => setIsOpen(false) };
}

function MyComponent() {
  const dialog = useDialogState();
  
  return (
    <>
      <button onClick={dialog.open}>Open Dialog</button>
      {dialog.isOpen && (
        <Dialog title="My Dialog" onClose={dialog.close}>
          Content here
        </Dialog>
      )}
    </>
  );
}
Warning: React does NOT recommend using inheritance for component reuse. Props and composition give you all the flexibility you need. Use hooks for behavior reuse, not class inheritance.

Advanced Component Patterns Best Practices

14. React Suspense and Concurrent Features REACT 18+

14.1 Suspense Component for Code Splitting

Feature Syntax Description Use Case
React.lazy() lazy(() => import('./Comp')) Dynamic import for code splitting Load components only when needed
Suspense <Suspense fallback={...}> Show fallback while lazy component loads Loading states for async components
fallback Prop fallback={<Spinner/>} Component shown during loading Skeleton screens, spinners, placeholders
Nested Suspense <Suspense>...<Suspense> Multiple suspense boundaries at different levels Granular loading states, progressive loading
Named Imports lazy(() => import().then()) Load named exports with lazy Non-default exports from modules

Example: Basic code splitting with Suspense

import { lazy, Suspense } from 'react';

// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <div>
      <Header /> {/* Always loaded */}
      
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </div>
  );
}

// Named export lazy loading
const MyComponent = lazy(() =>
  import('./MyModule').then(module => ({
    default: module.MyNamedExport
  }))
);

Example: Nested Suspense boundaries for granular loading

const MainContent = lazy(() => import('./MainContent'));
const Sidebar = lazy(() => import('./Sidebar'));
const Comments = lazy(() => import('./Comments'));

function Page() {
  return (
    <div>
      {/* Top-level suspense for entire page */}
      <Suspense fallback={<PageSkeleton />}>
        <div className="layout">
          {/* Separate boundary for main content */}
          <Suspense fallback={<ContentSkeleton />}>
            <MainContent />
            
            {/* Nested boundary for comments */}
            <Suspense fallback={<div>Loading comments...</div>}>
              <Comments />
            </Suspense>
          </Suspense>
          
          {/* Separate boundary for sidebar */}
          <Suspense fallback={<SidebarSkeleton />}>
            <Sidebar />
          </Suspense>
        </div>
      </Suspense>
    </div>
  );
}

// Route-based code splitting
function App() {
  return (
    <Router>
      <Suspense fallback={<FullPageSpinner />}>
        <Routes>
          <Route path="/" element={lazy(() => import('./Home'))} />
          <Route path="/about" element={lazy(() => import('./About'))} />
        </Routes>
      </Suspense>
    </Router>
  );
}
Code Splitting Best Practices: Split at route level first, use nested Suspense for progressive loading, show meaningful fallbacks (skeletons better than spinners), preload critical routes, split large third-party libraries separately.

14.2 Suspense for Data Fetching Patterns

Pattern Implementation Description Status
Suspense-enabled Libraries React Query, SWR, Relay Libraries with built-in Suspense support Stable
use() Hook REACT 19 const data = use(promise) Read promise value, suspends until resolved React 19+
Resource Pattern Wrap promise in resource object Manual Suspense implementation pattern Advanced
Server Components Async components on server Native async/await in components Next.js 13+
Streaming SSR renderToPipeableStream Stream HTML with Suspense boundaries React 18+

Example: Suspense with React Query

import { useQuery } from '@tanstack/react-query';
import { Suspense } from 'react';

// Component that uses Suspense for data fetching
function UserProfile({ userId }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    suspense: true  // Enable Suspense mode
  });
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

// Wrap with Suspense
function App() {
  return (
    <Suspense fallback={<UserSkeleton />}>
      <UserProfile userId={123} />
    </Suspense>
  );
}

// Multiple parallel data fetches with Suspense
function Dashboard() {
  return (
    <Suspense fallback={<DashboardSkeleton />}>
      <div className="dashboard">
        <Suspense fallback={<div>Loading stats...</div>}>
          <Stats />
        </Suspense>
        
        <Suspense fallback={<div>Loading activity...</div>}>
          <RecentActivity />
        </Suspense>
        
        <Suspense fallback={<div>Loading chart...</div>}>
          <Chart />
        </Suspense>
      </div>
    </Suspense>
  );
}

Example: use() hook for Suspense data fetching (React 19)

import { use, Suspense } from 'react';

// Fetch function returns a promise
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// Component using use() hook
function UserProfile({ userPromise }) {
  // use() suspends component until promise resolves
  const user = use(userPromise);
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

// Parent component
function App() {
  // Start fetch immediately (not in component)
  const userPromise = fetchUser(123);
  
  return (
    <Suspense fallback={<LoadingSkeleton />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

// Conditional data fetching with use()
function ConditionalData({ showData }) {
  if (!showData) {
    return <div>Enable data display</div>;
  }
  
  // use() can be called conditionally (unlike hooks!)
  const data = use(fetchData());
  return <div>{data.content}</div>;
}
Warning: Suspense for data fetching requires libraries with Suspense integration (React Query, SWR, Relay) or React 19's use() hook. Don't throw promises manually unless implementing a proper resource pattern.

14.3 Error Boundaries with Suspense

Concept Implementation Description Use Case
Error Boundary Wrapper Wrap Suspense with ErrorBoundary Catch errors from suspended components Handle loading failures gracefully
Separate Boundaries ErrorBoundary per Suspense Independent error handling for sections Prevent whole page failure
Retry Logic Reset error boundary state Allow user to retry failed loads Temporary network issues, transient errors
Error Fallback UI Custom error components User-friendly error messages Better UX than blank screens

Example: Error Boundary with Suspense integration

import { ErrorBoundary } from 'react-error-boundary';
import { Suspense } from 'react';

// Error fallback component with retry
function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert" className="error-container">
      <h2>Something went wrong</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>
        Try again
      </button>
    </div>
  );
}

// Combined Error + Suspense boundary
function Page() {
  return (
    <ErrorBoundary 
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // Reset any cached data
        queryClient.resetQueries();
      }}
    >
      <Suspense fallback={<LoadingSkeleton />}>
        <UserProfile />
      </Suspense>
    </ErrorBoundary>
  );
}

// Multiple boundaries for granular error handling
function Dashboard() {
  return (
    <div className="dashboard">
      {/* Each section has its own error boundary */}
      <ErrorBoundary fallback={<SectionError section="Stats" />}>
        <Suspense fallback={<StatsSkeleton />}>
          <Stats />
        </Suspense>
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<SectionError section="Activity" />}>
        <Suspense fallback={<ActivitySkeleton />}>
          <RecentActivity />
        </Suspense>
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<SectionError section="Chart" />}>
        <Suspense fallback={<ChartSkeleton />}>
          <Chart />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

Example: Reusable boundary component

// Combined ErrorBoundary + Suspense component
function AsyncBoundary({ 
  children, 
  fallback, 
  errorFallback,
  onReset 
}) {
  return (
    <ErrorBoundary 
      fallbackRender={({ error, resetErrorBoundary }) => 
        errorFallback ? 
          errorFallback(error, resetErrorBoundary) : 
          <DefaultErrorFallback error={error} reset={resetErrorBoundary} />
      }
      onReset={onReset}
    >
      <Suspense fallback={fallback || <DefaultLoadingFallback />}>
        {children}
      </Suspense>
    </ErrorBoundary>
  );
}

// Usage - clean and simple
function App() {
  return (
    <AsyncBoundary
      fallback={<PageSkeleton />}
      errorFallback={(error, reset) => (
        <PageError error={error} onRetry={reset} />
      )}
    >
      <UserDashboard />
    </AsyncBoundary>
  );
}
Error Boundary + Suspense Pattern: Always wrap Suspense with ErrorBoundary, use granular boundaries to prevent cascade failures, implement retry logic, show helpful error messages, log errors to monitoring services.

14.4 Concurrent Rendering and Time Slicing

Feature Description Benefit Availability
Concurrent Mode React can interrupt, pause, resume rendering Keeps app responsive during expensive renders React 18+
Time Slicing Break rendering work into chunks Yield to browser between chunks for interactions Automatic with React 18
Automatic Batching Batch multiple state updates together Fewer re-renders, better performance Enabled by default React 18
Interruptible Rendering React can pause low-priority work High-priority updates don't get blocked With concurrent features
Priority Levels Urgent vs non-urgent updates Keep UI responsive for user input useTransition, useDeferredValue

Example: Concurrent rendering with createRoot

import { createRoot } from 'react-dom/client';

// React 18+ concurrent mode (default with createRoot)
const root = createRoot(document.getElementById('root'));
root.render(<App />);

// Automatic batching in React 18
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  function handleClick() {
    // React 18 batches these together automatically
    // Even in async callbacks, timeouts, native events!
    setCount(c => c + 1);
    setFlag(f => !f);
    // Only 1 re-render instead of 2
  }
  
  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? 'blue' : 'black' }}>
        {count}
      </h1>
    </div>
  );
}

// Opt-out of batching if needed (rare)
import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // DOM updated immediately
  
  flushSync(() => {
    setFlag(f => !f);
  });
  // DOM updated again - 2 separate renders
}

Example: Visualizing concurrent rendering benefits

// Without concurrent features (React 17)
// Long render blocks everything
function HeavyList({ items }) {
  // This blocks the entire UI during render
  return (
    <ul>
      {items.map(item => (
        <ExpensiveItem key={item.id} item={item} />
      ))}
    </ul>
  );
}
// User can't type or interact until render completes

// With concurrent features (React 18+)
function ResponsiveList({ items }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [displayItems, setDisplayItems] = useState(items);
  
  function handleSearch(e) {
    const value = e.target.value;
    setQuery(value); // Urgent - updates immediately
    
    // Non-urgent - can be interrupted
    startTransition(() => {
      const filtered = items.filter(item =>
        item.name.includes(value)
      );
      setDisplayItems(filtered);
    });
  }
  
  return (
    <div>
      <input value={query} onChange={handleSearch} />
      {isPending && <Spinner />}
      <ul className={isPending ? 'loading' : ''}>
        {displayItems.map(item => (
          <ExpensiveItem key={item.id} item={item} />
        ))}
      </ul>
    </div>
  );
}
// Input stays responsive - filtering happens in background
Concurrent Features: Enabled by default with createRoot in React 18, automatic batching improves performance automatically, time slicing keeps UI responsive, use transitions for non-urgent updates, no breaking changes for existing code.

14.5 useTransition for State Transitions

Feature Syntax Description Use Case
useTransition Hook const [isPending, start] = useTransition() Mark state updates as non-urgent transitions Keep UI responsive during expensive updates
isPending Flag isPending Boolean indicating if transition is in progress Show loading indicators, disable UI temporarily
startTransition startTransition(() => {...}) Function to wrap non-urgent state updates Large list filters, tab switches, searches
Priority Lower than user input Can be interrupted by urgent updates Heavy renders don't block interactions
import { useState, useTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  function handleSearch(e) {
    const value = e.target.value;
    
    // Urgent: Update input immediately
    setQuery(value);
    
    // Non-urgent: Update results (can be interrupted)
    startTransition(() => {
      // Expensive filtering
      const filtered = allItems.filter(item =>
        item.title.toLowerCase().includes(value.toLowerCase()) ||
        item.description.toLowerCase().includes(value.toLowerCase())
      );
      setResults(filtered);
    });
  }
  
  return (
    <div>
      <input 
        type="text"
        value={query}
        onChange={handleSearch}
        placeholder="Search..."
      />
      
      {isPending && <LoadingSpinner />}
      
      <ul className={isPending ? 'opacity-50' : ''}>
        {results.map(item => (
          <SearchResultItem key={item.id} item={item} />
        ))}
      </ul>
    </div>
  );
}

Example: Tab switching with transitions

function Tabs() {
  const [activeTab, setActiveTab] = useState('home');
  const [isPending, startTransition] = useTransition();
  
  function handleTabClick(tab) {
    // Mark tab content update as transition
    startTransition(() => {
      setActiveTab(tab);
    });
  }
  
  return (
    <div>
      <div className="tab-buttons">
        <button 
          onClick={() => handleTabClick('home')}
          className={activeTab === 'home' ? 'active' : ''}
        >
          Home
        </button>
        <button 
          onClick={() => handleTabClick('posts')}
          className={activeTab === 'posts' ? 'active' : ''}
        >
          Posts {isPending && '...'}
        </button>
        <button 
          onClick={() => handleTabClick('contact')}
          className={activeTab === 'contact' ? 'active' : ''}
        >
          Contact
        </button>
      </div>
      
      <div className={`tab-content ${isPending ? 'loading' : ''}`}>
        {activeTab === 'home' && <HomeTab />}
        {activeTab === 'posts' && <PostsTab />} {/* Expensive */}
        {activeTab === 'contact' && <ContactTab />}
      </div>
    </div>
  );
}

// Comparison: without useTransition
function SlowTabs() {
  const [activeTab, setActiveTab] = useState('home');
  
  // Tab clicks feel laggy because UI waits for expensive render
  return (
    <div>
      <button onClick={() => setActiveTab('posts')}>
        Posts
      </button>
      {/* UI freezes until PostsTab renders */}
      {activeTab === 'posts' && <PostsTab />}
    </div>
  );
}

Example: Pagination with transitions

function PaginatedList() {
  const [page, setPage] = useState(1);
  const [isPending, startTransition] = useTransition();
  
  // Fetch data for current page
  const { data, loading } = useFetch(`/api/items?page=${page}`);
  
  function goToPage(newPage) {
    startTransition(() => {
      setPage(newPage);
    });
  }
  
  return (
    <div>
      {/* Show current data while transitioning */}
      <div className={isPending ? 'transitioning' : ''}>
        {loading ? (
          <Spinner />
        ) : (
          <ItemList items={data} />
        )}
      </div>
      
      <div className="pagination">
        <button 
          onClick={() => goToPage(page - 1)}
          disabled={page === 1 || isPending}
        >
          Previous
        </button>
        
        <span>Page {page} {isPending && '(loading...)'}</span>
        
        <button 
          onClick={() => goToPage(page + 1)}
          disabled={isPending}
        >
          Next
        </button>
      </div>
    </div>
  );
}
useTransition Best Practices: Use for expensive updates that aren't time-sensitive, keep user input responsive (don't wrap input onChange), show isPending state to user, works with Suspense boundaries, test on low-end devices to see benefits.

14.6 Server Suspense and Streaming SSR

Feature API Description Use Case
Streaming SSR renderToPipeableStream() Stream HTML to client progressively Faster initial page load, better TTFB
Selective Hydration Automatic with Suspense Hydrate critical parts first Interactive faster, better TTI
Server Suspense <Suspense> on server Show fallback while server fetches data Stream page with loading states
Server Components Async components (Next.js) Await data directly in components Zero-bundle data fetching
Progressive Loading Nested Suspense boundaries Stream page sections independently Show content as it's ready

Example: Node.js streaming SSR setup

import { renderToPipeableStream } from 'react-dom/server';

// Server-side streaming setup
app.get('/', (req, res) => {
  const { pipe, abort } = renderToPipeableStream(
    <App />,
    {
      bootstrapScripts: ['/main.js'],
      onShellReady() {
        // Start streaming immediately
        res.setHeader('Content-Type', 'text/html');
        pipe(res);
      },
      onShellError(error) {
        res.status(500).send('<h1>Server Error</h1>');
      },
      onAllReady() {
        // All Suspense boundaries resolved
        console.log('All content ready');
      },
      onError(error) {
        console.error('Stream error:', error);
      }
    }
  );
  
  // Abort after timeout
  setTimeout(() => abort(), 10000);
});

// App with Suspense boundaries
function App() {
  return (
    <html>
      <head>
        <title>Streaming SSR App</title>
      </head>
      <body>
        <Header /> {/* Rendered immediately */}
        
        {/* Stream this section separately */}
        <Suspense fallback={<CommentsSkeleton />}>
          <Comments /> {/* Slow data fetch */}
        </Suspense>
        
        <Footer /> {/* Rendered immediately */}
      </body>
    </html>
  );
}

Example: Next.js Server Components with Suspense

// app/page.tsx (Next.js 13+ App Router)
import { Suspense } from 'react';

// Server Component - can be async!
async function UserProfile({ userId }) {
  // Fetch directly in component (server-side)
  const user = await fetch(`https://api.example.com/users/${userId}`)
    .then(res => res.json());
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

async function Posts() {
  const posts = await fetch('https://api.example.com/posts')
    .then(res => res.json());
  
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// Page with streaming sections
export default function Page() {
  return (
    <div>
      <h1>My Page</h1>
      
      {/* Profile streams first */}
      <Suspense fallback={<ProfileSkeleton />}>
        <UserProfile userId="123" />
      </Suspense>
      
      {/* Posts stream independently */}
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
    </div>
  );
}
// HTML streams progressively as data arrives

Example: Selective hydration benefits

// Page with multiple sections
function App() {
  return (
    <div>
      <Header /> {/* Hydrates first (small, fast) */}
      
      {/* Heavy component wrapped in Suspense */}
      <Suspense fallback={<ChartSkeleton />}>
        <HeavyChart /> {/* Hydrates later, doesn't block */}
      </Suspense>
      
      {/* User can interact with button before chart hydrates */}
      <button onClick={handleClick}>
        Click me (interactive immediately!)
      </button>
      
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments /> {/* Hydrates after chart */}
      </Suspense>
    </div>
  );
}

// Benefits of selective hydration:
// 1. Faster Time to Interactive (TTI)
// 2. User can interact with parts before full hydration
// 3. Heavy components don't block critical interactions
// 4. Automatic priority based on user interaction
Streaming SSR Benefits: Faster TTFB (Time to First Byte), progressive page rendering, selective hydration for better TTI, better perceived performance, works with Suspense boundaries, built into Next.js 13+ App Router.

Suspense and Concurrent Features Best Practices

15. Portals and DOM Rendering Control

15.1 createPortal for DOM Teleportation

Feature Syntax Description Use Case
createPortal createPortal(child, container) Render children into different DOM node Modals, tooltips, dropdowns outside parent DOM
Target Container document.getElementById('id') DOM element to render portal into Dedicated portal root, body, custom container
Event Bubbling Bubbles through React tree Events bubble to React parent, not DOM parent Event handlers work as expected in React
Context Inherits from React tree Portal components access parent context Theme, auth, state available in portals
Multiple Portals Multiple createPortal calls Render to different containers Multiple modals, tooltips, overlays

Example: Basic portal setup

import { createPortal } from 'react-dom';

// HTML structure needed
// <div id="root"></div>
// <div id="portal-root"></div>

function App() {
  return (
    <div className="app">
      <h1>My App</h1>
      <PortalExample />
    </div>
  );
}

function PortalExample() {
  const [show, setShow] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShow(true)}>
        Show Portal
      </button>
      
      {show && (
        createPortal(
          <div className="portal-content">
            <h2>I'm rendered in #portal-root!</h2>
            <button onClick={() => setShow(false)}>
              Close
            </button>
          </div>,
          document.getElementById('portal-root')
        )
      )}
    </div>
  );
}

// Portal component wrapper
function Portal({ children, container = 'portal-root' }) {
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
    return () => setMounted(false);
  }, []);
  
  if (!mounted) return null;
  
  const portalRoot = document.getElementById(container);
  if (!portalRoot) {
    console.error(`Portal container #${container} not found`);
    return null;
  }
  
  return createPortal(children, portalRoot);
}

Example: Dynamic portal container

// Create portal container on demand
function usePortal(id = 'portal-root') {
  const [container, setContainer] = useState(null);
  
  useEffect(() => {
    // Check if container exists
    let element = document.getElementById(id);
    
    // Create if doesn't exist
    if (!element) {
      element = document.createElement('div');
      element.id = id;
      document.body.appendChild(element);
    }
    
    setContainer(element);
    
    // Cleanup - remove if we created it
    return () => {
      if (element && element.childNodes.length === 0) {
        element.remove();
      }
    };
  }, [id]);
  
  return container;
}

// Usage
function MyComponent() {
  const portalContainer = usePortal('my-portal');
  
  if (!portalContainer) return null;
  
  return createPortal(
    <div>Portal content</div>,
    portalContainer
  );
}
Portal Benefits: Render outside parent DOM hierarchy, avoid CSS overflow/z-index issues, maintain React component tree structure, events bubble through React tree, access parent context and props.

15.2 Modal Components with Portals

Feature Implementation Description Benefit
Overlay/Backdrop Full-screen div with fixed position Semi-transparent background behind modal Focus attention, prevent background interaction
Body Scroll Lock document.body.style.overflow Disable scrolling when modal open Keep user focused on modal content
Focus Trap Manage focus within modal Tab cycles through modal elements only Keyboard accessibility, prevents focus escape
ESC to Close Keyboard event listener Close modal on Escape key Expected behavior, better UX
Click Outside Backdrop click handler Close modal when clicking backdrop Intuitive dismissal, flexible UX
import { createPortal } from 'react-dom';
import { useEffect, useRef } from 'react';

function Modal({ isOpen, onClose, children, title }) {
  const modalRef = useRef(null);
  
  // Lock body scroll when modal open
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
      return () => {
        document.body.style.overflow = 'unset';
      };
    }
  }, [isOpen]);
  
  // Close on ESC key
  useEffect(() => {
    if (!isOpen) return;
    
    const handleEscape = (e) => {
      if (e.key === 'Escape') onClose();
    };
    
    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [isOpen, onClose]);
  
  // Focus trap
  useEffect(() => {
    if (!isOpen || !modalRef.current) return;
    
    const focusableElements = modalRef.current.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
    // Focus first element
    firstElement?.focus();
    
    const handleTab = (e) => {
      if (e.key !== 'Tab') return;
      
      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          e.preventDefault();
          lastElement?.focus();
        }
      } else {
        if (document.activeElement === lastElement) {
          e.preventDefault();
          firstElement?.focus();
        }
      }
    };
    
    document.addEventListener('keydown', handleTab);
    return () => document.removeEventListener('keydown', handleTab);
  }, [isOpen]);
  
  if (!isOpen) return null;
  
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div 
        ref={modalRef}
        className="modal-content"
        onClick={(e) => e.stopPropagation()} // Prevent close on content click
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
      >
        <div className="modal-header">
          <h2 id="modal-title">{title}</h2>
          <button 
            onClick={onClose}
            aria-label="Close modal"
            className="modal-close"
          >
            ×
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    document.body
  );
}

// Usage
function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsModalOpen(true)}>
        Open Modal
      </button>
      
      <Modal 
        isOpen={isModalOpen}
        onClose={() => setIsModalOpen(false)}
        title="My Modal"
      >
        <p>Modal content goes here</p>
        <button onClick={() => setIsModalOpen(false)}>
          Close
        </button>
      </Modal>
    </div>
  );
}

Example: Modal with animation

import { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';

function AnimatedModal({ isOpen, onClose, children }) {
  const [shouldRender, setShouldRender] = useState(false);
  const [isAnimating, setIsAnimating] = useState(false);
  
  useEffect(() => {
    if (isOpen) {
      setShouldRender(true);
      // Trigger animation after render
      setTimeout(() => setIsAnimating(true), 10);
    } else {
      setIsAnimating(false);
      // Remove from DOM after animation
      setTimeout(() => setShouldRender(false), 300);
    }
  }, [isOpen]);
  
  if (!shouldRender) return null;
  
  return createPortal(
    <div 
      className={`modal-overlay ${isAnimating ? 'open' : ''}`}
      onClick={onClose}
    >
      <div 
        className={`modal-content ${isAnimating ? 'open' : ''}`}
        onClick={(e) => e.stopPropagation()}
      >
        {children}
      </div>
    </div>,
    document.body
  );
}

/* CSS for animation
.modal-overlay {
  opacity: 0;
  transition: opacity 300ms ease-in-out;
}

.modal-overlay.open {
  opacity: 1;
}

.modal-content {
  transform: scale(0.7);
  opacity: 0;
  transition: all 300ms ease-in-out;
}

.modal-content.open {
  transform: scale(1);
  opacity: 1;
}
*/
Warning: Always lock body scroll, implement keyboard accessibility (ESC, focus trap), use proper ARIA attributes, clean up event listeners, test with screen readers.

15.3 Tooltip and Overlay Positioning

Technique Implementation Description Use Case
getBoundingClientRect Element position calculation Get trigger element position and size Position tooltip relative to trigger
Positioning Library Popper.js, Floating UI Smart positioning with collision detection Complex tooltips, dropdowns, popovers
Dynamic Position Calculate on show/scroll/resize Update position when trigger moves Scrollable containers, responsive layouts
Flip/Shift Adjust if outside viewport Prevent tooltip from being cut off Edges of screen, small viewports
Arrow Positioning Calculated arrow pointer position Arrow points to trigger element Visual connection between elements

Example: Basic tooltip with positioning

import { useState, useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';

function Tooltip({ children, content, position = 'top' }) {
  const [isVisible, setIsVisible] = useState(false);
  const [coords, setCoords] = useState({ top: 0, left: 0 });
  const triggerRef = useRef(null);
  const tooltipRef = useRef(null);
  
  const calculatePosition = () => {
    if (!triggerRef.current || !tooltipRef.current) return;
    
    const triggerRect = triggerRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();
    
    let top = 0;
    let left = 0;
    const gap = 8; // Space between trigger and tooltip
    
    switch (position) {
      case 'top':
        top = triggerRect.top - tooltipRect.height - gap;
        left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
        break;
      case 'bottom':
        top = triggerRect.bottom + gap;
        left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
        break;
      case 'left':
        top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
        left = triggerRect.left - tooltipRect.width - gap;
        break;
      case 'right':
        top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
        left = triggerRect.right + gap;
        break;
    }
    
    // Prevent overflow
    top = Math.max(gap, Math.min(top, window.innerHeight - tooltipRect.height - gap));
    left = Math.max(gap, Math.min(left, window.innerWidth - tooltipRect.width - gap));
    
    setCoords({ top, left });
  };
  
  useEffect(() => {
    if (isVisible) {
      calculatePosition();
      window.addEventListener('scroll', calculatePosition);
      window.addEventListener('resize', calculatePosition);
      
      return () => {
        window.removeEventListener('scroll', calculatePosition);
        window.removeEventListener('resize', calculatePosition);
      };
    }
  }, [isVisible]);
  
  return (
    <>
      <span
        ref={triggerRef}
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
        onFocus={() => setIsVisible(true)}
        onBlur={() => setIsVisible(false)}
      >
        {children}
      </span>
      
      {isVisible && createPortal(
        <div
          ref={tooltipRef}
          className="tooltip"
          role="tooltip"
          style={{
            position: 'fixed',
            top: `${coords.top}px`,
            left: `${coords.left}px`,
            zIndex: 9999
          }}
        >
          {content}
        </div>,
        document.body
      )}
    </>
  );
}

// Usage
<p>
  Hover over <Tooltip content="This is a tooltip!">
    <strong>this text</strong>
  </Tooltip> to see tooltip.
</p>

Example: Advanced positioning with Floating UI

import { useFloating, flip, shift, offset, arrow } from '@floating-ui/react';
import { useRef } from 'react';
import { createPortal } from 'react-dom';

function AdvancedTooltip({ children, content }) {
  const [isOpen, setIsOpen] = useState(false);
  const arrowRef = useRef(null);
  
  const { x, y, strategy, refs, middlewareData } = useFloating({
    open: isOpen,
    placement: 'top',
    middleware: [
      offset(8), // Gap between trigger and tooltip
      flip(), // Flip to opposite side if no space
      shift({ padding: 8 }), // Shift to stay in viewport
      arrow({ element: arrowRef }) // Position arrow
    ]
  });
  
  const arrowX = middlewareData.arrow?.x;
  const arrowY = middlewareData.arrow?.y;
  
  return (
    <>
      <span
        ref={refs.setReference}
        onMouseEnter={() => setIsOpen(true)}
        onMouseLeave={() => setIsOpen(false)}
      >
        {children}
      </span>
      
      {isOpen && createPortal(
        <div
          ref={refs.setFloating}
          style={{
            position: strategy,
            top: y ?? 0,
            left: x ?? 0,
            zIndex: 9999
          }}
          className="tooltip"
        >
          {content}
          <div
            ref={arrowRef}
            style={{
              position: 'absolute',
              left: arrowX != null ? `${arrowX}px` : '',
              top: arrowY != null ? `${arrowY}px` : '',
              transform: 'rotate(45deg)'
            }}
            className="tooltip-arrow"
          />
        </div>,
        document.body
      )}
    </>
  );
}
Positioning Best Practices: Use positioning libraries for complex cases, recalculate on scroll/resize, prevent viewport overflow, add collision detection, position arrow correctly, test on mobile devices.

15.4 Event Bubbling and Portal Event Handling

Concept Behavior Description Implication
React Tree Bubbling Events bubble through React tree Not DOM tree - React component hierarchy Parent handlers work even though DOM separate
onClick Propagation Bubbles to React parent Portal click triggers parent onClick Event handlers work naturally
stopPropagation e.stopPropagation() Stop event from bubbling in React tree Prevent parent handlers from firing
Context Access Portal accesses parent context Context flows through React tree Portal components have full context access
Native Events Don't bubble through portals addEventListener on DOM doesn't cross portal Use React events, not native listeners

Example: Event bubbling demonstration

import { createPortal } from 'react-dom';

function EventBubblingDemo() {
  const [log, setLog] = useState([]);
  
  const addLog = (message) => {
    setLog(prev => [...prev, message]);
  };
  
  return (
    <div 
      onClick={() => addLog('Parent clicked')}
      style={{ padding: '20px', border: '2px solid blue' }}
    >
      <h3>Parent Component</h3>
      
      <button onClick={() => addLog('Regular button clicked')}>
        Regular Button (in DOM tree)
      </button>
      
      {createPortal(
        <div 
          style={{ 
            position: 'fixed', 
            top: '100px', 
            right: '20px',
            padding: '20px',
            border: '2px solid red',
            background: 'white'
          }}
        >
          <h3>Portal Component</h3>
          <p>I'm rendered outside the parent DOM!</p>
          
          {/* This click WILL bubble to parent in React tree */}
          <button onClick={() => addLog('Portal button clicked')}>
            Portal Button
          </button>
          
          {/* This prevents bubbling */}
          <button onClick={(e) => {
            e.stopPropagation();
            addLog('Portal button (no bubble) clicked');
          }}>
            Portal Button (stopPropagation)
          </button>
        </div>,
        document.body
      )}
      
      <div style={{ marginTop: '20px' }}>
        <h4>Event Log:</h4>
        <ul>
          {log.map((item, i) => (
            <li key={i}>{item}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

// Result: Portal button click triggers BOTH:
// 1. "Portal button clicked"
// 2. "Parent clicked" (bubbled through React tree)

// But "Portal button (no bubble)" only triggers:
// 1. "Portal button (no bubble) clicked"
// (stopPropagation prevents parent handler)

Example: Context access in portals

import { createContext, useContext } from 'react';
import { createPortal } from 'react-dom';

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={theme}>
      <div className={`app ${theme}`}>
        <h1>Main App</h1>
        <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
          Toggle Theme
        </button>
        
        <PortalComponent />
      </div>
    </ThemeContext.Provider>
  );
}

function PortalComponent() {
  // Portal component can access parent context!
  const theme = useContext(ThemeContext);
  
  return createPortal(
    <div className={`portal ${theme}`}>
      <h2>I'm in a portal</h2>
      <p>Current theme: {theme}</p>
      <p>Context works across portals!</p>
    </div>,
    document.body
  );
}

// Theme updates in parent flow to portal component
// Even though rendered in different DOM location
Event Bubbling Key Points: React events bubble through component tree not DOM tree, portal events reach parent handlers naturally, use stopPropagation to prevent bubbling, context and props work across portals, native DOM events don't cross portal boundary.

15.5 Accessibility Considerations with Portals

Concern Solution ARIA Attribute Why Important
Screen Reader Announcement Use proper role attributes role="dialog" Announce modal/dialog to screen readers
Modal State Indicate modal is active aria-modal="true" Screen reader understands context
Label Association Link title to dialog aria-labelledby Identify dialog purpose
Focus Management Move focus to modal Focus first element Keyboard users start in modal
Focus Trap Keep focus within modal Tab cycling Prevent accessing hidden content
Return Focus Restore focus after close Store trigger element User returns to where they were
Hidden Content Mark background as hidden aria-hidden="true" Screen readers skip background

Example: Accessible modal with full ARIA support

import { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';

function AccessibleModal({ isOpen, onClose, title, children }) {
  const modalRef = useRef(null);
  const triggerRef = useRef(document.activeElement);
  const mainContentRef = useRef(document.getElementById('main-content'));
  
  // Focus management
  useEffect(() => {
    if (!isOpen) return;
    
    // Store trigger element
    triggerRef.current = document.activeElement;
    
    // Hide main content from screen readers
    if (mainContentRef.current) {
      mainContentRef.current.setAttribute('aria-hidden', 'true');
      mainContentRef.current.setAttribute('inert', ''); // Prevent interaction
    }
    
    // Focus first focusable element in modal
    const focusable = modalRef.current?.querySelector(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    focusable?.focus();
    
    return () => {
      // Restore main content
      if (mainContentRef.current) {
        mainContentRef.current.removeAttribute('aria-hidden');
        mainContentRef.current.removeAttribute('inert');
      }
      
      // Return focus to trigger
      if (triggerRef.current instanceof HTMLElement) {
        triggerRef.current.focus();
      }
    };
  }, [isOpen]);
  
  // ESC and focus trap
  useEffect(() => {
    if (!isOpen || !modalRef.current) return;
    
    const handleKeyDown = (e) => {
      // Close on ESC
      if (e.key === 'Escape') {
        onClose();
        return;
      }
      
      // Focus trap
      if (e.key === 'Tab') {
        const focusableElements = modalRef.current.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
        
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        if (e.shiftKey) {
          if (document.activeElement === firstElement) {
            e.preventDefault();
            lastElement?.focus();
          }
        } else {
          if (document.activeElement === lastElement) {
            e.preventDefault();
            firstElement?.focus();
          }
        }
      }
    };
    
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [isOpen, onClose]);
  
  if (!isOpen) return null;
  
  return createPortal(
    <div 
      className="modal-overlay"
      onClick={onClose}
      role="presentation"
    >
      <div
        ref={modalRef}
        className="modal-content"
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
        aria-describedby="modal-description"
      >
        <div className="modal-header">
          <h2 id="modal-title">{title}</h2>
          <button
            onClick={onClose}
            aria-label="Close dialog"
            className="modal-close"
          >
            <span aria-hidden="true">×</span>
          </button>
        </div>
        
        <div id="modal-description" className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    document.body
  );
}

Example: Accessible tooltip

function AccessibleTooltip({ children, content, id }) {
  const [isVisible, setIsVisible] = useState(false);
  const tooltipId = id || `tooltip-${Math.random().toString(36).substr(2, 9)}`;
  
  return (
    <>
      <span
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
        onFocus={() => setIsVisible(true)}
        onBlur={() => setIsVisible(false)}
        aria-describedby={isVisible ? tooltipId : undefined}
        tabIndex={0} // Make keyboard accessible
      >
        {children}
      </span>
      
      {isVisible && createPortal(
        <div
          id={tooltipId}
          role="tooltip"
          className="tooltip"
          style={{
            position: 'fixed',
            // ... positioning logic
          }}
        >
          {content}
        </div>,
        document.body
      )}
    </>
  );
}

// ARIA attributes explained:
// - aria-describedby: Links tooltip to trigger
// - role="tooltip": Identifies as tooltip
// - tabIndex={0}: Makes keyboard accessible
Accessibility Requirements: Always manage focus properly, implement keyboard navigation (ESC, Tab), use proper ARIA attributes, hide background content, return focus on close, test with screen readers (NVDA, JAWS, VoiceOver), support keyboard-only navigation.

15.6 Multiple Portal Management Patterns

Pattern Implementation Description Use Case
Portal Stack Array of active portals Track z-index order of portals Multiple modals, layered overlays
Portal Manager Context-based management Centralized portal state and control Complex apps with many portals
Modal Queue Queue system for modals Show modals sequentially Onboarding flows, tutorials
Z-Index Management Auto-increment z-index Ensure correct stacking order Prevent overlap issues
Cleanup on Unmount Remove portal containers Clean up DOM when done Prevent memory leaks, clean DOM

Example: Portal manager with context

import { createContext, useContext, useState } from 'react';
import { createPortal } from 'react-dom';

// Portal manager context
const PortalContext = createContext(null);

export function PortalProvider({ children }) {
  const [portals, setPortals] = useState([]);
  
  const addPortal = (portal) => {
    const id = Math.random().toString(36).substr(2, 9);
    setPortals(prev => [...prev, { ...portal, id, zIndex: 1000 + prev.length }]);
    return id;
  };
  
  const removePortal = (id) => {
    setPortals(prev => prev.filter(p => p.id !== id));
  };
  
  const closeTopPortal = () => {
    setPortals(prev => prev.slice(0, -1));
  };
  
  return (
    <PortalContext.Provider value={{ portals, addPortal, removePortal, closeTopPortal }}>
      {children}
      {portals.map(portal =>
        createPortal(
          <div key={portal.id} style={{ zIndex: portal.zIndex }}>
            {portal.content}
          </div>,
          document.body
        )
      )}
    </PortalContext.Provider>
  );
}

// Hook to use portal manager
export function usePortal() {
  const context = useContext(PortalContext);
  if (!context) {
    throw new Error('usePortal must be used within PortalProvider');
  }
  return context;
}

// Modal component using portal manager
function ManagedModal({ isOpen, onClose, children, title }) {
  const { addPortal, removePortal } = usePortal();
  const portalIdRef = useRef(null);
  
  useEffect(() => {
    if (isOpen) {
      portalIdRef.current = addPortal({
        content: (
          <div className="modal-overlay" onClick={onClose}>
            <div className="modal-content" onClick={(e) => e.stopPropagation()}>
              <h2>{title}</h2>
              {children}
            </div>
          </div>
        )
      });
    } else if (portalIdRef.current) {
      removePortal(portalIdRef.current);
      portalIdRef.current = null;
    }
    
    return () => {
      if (portalIdRef.current) {
        removePortal(portalIdRef.current);
      }
    };
  }, [isOpen, title, children]);
  
  return null;
}

// Usage
function App() {
  return (
    <PortalProvider>
      <MyApp />
    </PortalProvider>
  );
}

Example: Modal queue system

function useModalQueue() {
  const [queue, setQueue] = useState([]);
  const [currentModal, setCurrentModal] = useState(null);
  
  const addToQueue = (modal) => {
    setQueue(prev => [...prev, modal]);
  };
  
  const showNext = () => {
    if (queue.length > 0) {
      setCurrentModal(queue[0]);
      setQueue(prev => prev.slice(1));
    } else {
      setCurrentModal(null);
    }
  };
  
  const closeModal = () => {
    setCurrentModal(null);
    // Show next in queue after delay
    setTimeout(showNext, 300);
  };
  
  // Auto-show first modal
  useEffect(() => {
    if (!currentModal && queue.length > 0) {
      showNext();
    }
  }, [queue, currentModal]);
  
  return { currentModal, addToQueue, closeModal, queueLength: queue.length };
}

// Usage - onboarding flow
function OnboardingFlow() {
  const { currentModal, addToQueue, closeModal } = useModalQueue();
  
  useEffect(() => {
    // Queue multiple modals
    addToQueue({ title: 'Welcome', content: 'Step 1...' });
    addToQueue({ title: 'Features', content: 'Step 2...' });
    addToQueue({ title: 'Get Started', content: 'Step 3...' });
  }, []);
  
  return (
    <>
      {currentModal && (
        <Modal
          isOpen={true}
          onClose={closeModal}
          title={currentModal.title}
        >
          {currentModal.content}
        </Modal>
      )}
    </>
  );
}
Multiple Portal Best Practices: Use portal manager for complex apps, implement z-index management, handle ESC key to close top portal, clean up portals on unmount, consider modal queue for flows, test stacking behavior thoroughly.

Portals and DOM Rendering Best Practices

16. Server Components and Full-Stack React NEXT.JS 13+

16.1 React Server Components Fundamentals

Concept Description Characteristic Use Case
Server Components Components that run only on server No JavaScript sent to client, async by default Data fetching, database queries, backend logic
Client Components Traditional React components Run on both server (SSR) and client, need 'use client' Interactivity, hooks, browser APIs, event handlers
Zero Bundle Impact Server component code not in bundle Smaller JavaScript payload Large dependencies, utilities, formatters
Direct Backend Access Access databases, file system directly No API route needed Database queries, file reads, server-only logic
Async Components Can use async/await in component Natural data fetching pattern Fetch data directly in component body
Streaming Stream HTML progressively Works with Suspense boundaries Faster initial page load, progressive rendering

Example: Server Component basics

// app/page.tsx - Server Component by default (Next.js 13+)
// No 'use client' directive = Server Component

import { db } from '@/lib/database';

// Server components can be async!
async function ProductList() {
  // Direct database access - no API route needed
  const products = await db.product.findMany({
    include: { category: true }
  });
  
  return (
    <div>
      <h1>Products</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
            <span>{product.category.name}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

// Server Component characteristics:
// ✓ Can use async/await
// ✓ Direct database/filesystem access
// ✓ No JavaScript sent to client
// ✓ Can import server-only code
// ✓ Runs only on server
// ✗ Cannot use hooks (useState, useEffect, etc.)
// ✗ Cannot use browser APIs
// ✗ Cannot have event handlers

Example: Client Component with 'use client'

// components/Counter.tsx - Client Component
'use client'; // This directive marks it as a Client Component

import { useState } from 'react';

export function Counter() {
  // Can use hooks - runs on client
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      {/* Event handlers work */}
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

// Client Component characteristics:
// ✓ Can use hooks (useState, useEffect, etc.)
// ✓ Can use browser APIs
// ✓ Can have event handlers
// ✓ Interactive features
// ✗ Cannot be async
// ✗ JavaScript sent to client
// ✗ Cannot directly access server resources
Server vs Client Components: Server Components are the default in Next.js App Router, use 'use client' for interactivity, server components have zero bundle impact, can mix both in same app, server components can import client components (but not vice versa).

16.2 Client vs Server Component Boundaries

Rule Allowed Not Allowed Why
Importing Server can import Client Client cannot import Server Server code shouldn't run on client
Props Pass serializable props to Client Pass functions, class instances Props must cross network boundary
Children Pattern Pass Server as children to Client Import Server into Client Composition pattern preserves boundaries
Context Client Components can provide context Server Components cannot use context Context requires client-side state
Third-party Mark packages as 'use client' if needed Import packages without checking Many packages assume client environment

Example: Component composition patterns

// ✓ CORRECT: Server Component imports Client Component
// app/page.tsx (Server Component)
import { Counter } from '@/components/Counter'; // Client Component

export default function Page() {
  return (
    <div>
      <h1>My Page</h1>
      <Counter /> {/* Client Component works here */}
    </div>
  );
}

// ✗ WRONG: Client Component cannot import Server Component
// components/ClientComponent.tsx
'use client';
import { ServerData } from './ServerComponent'; // ERROR!

// ✓ CORRECT: Use composition with children prop
// components/ClientWrapper.tsx
'use client';

export function ClientWrapper({ children }) {
  const [state, setState] = useState(false);
  
  return (
    <div onClick={() => setState(!state)}>
      {children} {/* Server Component passed as children */}
    </div>
  );
}

// app/page.tsx (Server Component)
import { ClientWrapper } from '@/components/ClientWrapper';

async function ServerData() {
  const data = await fetchFromDatabase();
  return <div>{data}</div>;
}

export default function Page() {
  return (
    <ClientWrapper>
      <ServerData /> {/* Server Component passed to Client via children */}
    </ClientWrapper>
  );
}

Example: Serializable props requirement

// ✓ CORRECT: Serializable props
// app/page.tsx (Server Component)
<ClientComponent 
  name="John"
  age={30}
  items={['a', 'b', 'c']}
  config={{ theme: 'dark', locale: 'en' }}
/>

// ✗ WRONG: Non-serializable props
<ClientComponent 
  onClick={() => console.log('click')} // Functions can't be serialized
  date={new Date()} // Dates lose type information
  callback={myFunction} // Functions not allowed
/>

// ✓ CORRECT: Define handlers in Client Component
'use client';

export function ClientComponent({ name, age }) {
  // Define event handlers in client component
  const handleClick = () => {
    console.log(`${name} clicked, age: ${age}`);
  };
  
  return <button onClick={handleClick}>{name}</button>;
}

// ✓ CORRECT: Server Actions for server-side logic
// app/actions.ts
'use server';

export async function updateUser(formData) {
  // Server-side logic
  const name = formData.get('name');
  await db.user.update({ name });
}

// Client Component can call Server Action
'use client';
import { updateUser } from './actions';

export function Form() {
  return (
    <form action={updateUser}>
      <input name="name" />
      <button type="submit">Save</button>
    </form>
  );
}
Warning: Server Components cannot import Client Components as modules - use children prop pattern. Props must be serializable (no functions, class instances). Third-party packages may need 'use client' wrapper. Test boundaries carefully.

16.3 Data Fetching in Server Components

Pattern Implementation Benefit Use Case
Async Components async function Component() Natural async/await syntax Database queries, API calls, file reads
Parallel Fetching Promise.all([fetch1, fetch2]) Multiple requests simultaneously Independent data sources
Sequential Fetching Await each fetch separately Dependent requests Second request needs first's result
Streaming with Suspense Wrap slow components in Suspense Progressive page rendering Show fast content first, stream slow data
Request Memoization React dedupes identical requests Automatic caching during render Multiple components need same data
Server-only Code import 'server-only' Prevent client-side import Database clients, secret keys, server utilities

Example: Data fetching patterns

// Async Server Component
async function UserProfile({ userId }) {
  // Direct database access
  const user = await db.user.findUnique({
    where: { id: userId },
    include: { posts: true, followers: true }
  });
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <p>Posts: {user.posts.length}</p>
      <p>Followers: {user.followers.length}</p>
    </div>
  );
}

// Parallel data fetching
async function Dashboard() {
  // Fetch multiple resources in parallel
  const [user, posts, stats] = await Promise.all([
    db.user.findFirst(),
    db.post.findMany({ take: 10 }),
    db.analytics.getStats()
  ]);
  
  return (
    <div>
      <UserCard user={user} />
      <PostList posts={posts} />
      <StatsWidget stats={stats} />
    </div>
  );
}

// Sequential data fetching (when dependent)
async function PostWithComments({ postId }) {
  // First, get the post
  const post = await db.post.findUnique({
    where: { id: postId }
  });
  
  // Then, get comments (needs post.id)
  const comments = await db.comment.findMany({
    where: { postId: post.id }
  });
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <CommentList comments={comments} />
    </article>
  );
}

Example: Streaming with Suspense

// app/page.tsx
import { Suspense } from 'react';

// Fast component
async function QuickData() {
  const data = await fetch('https://api.example.com/quick');
  return <div>Quick: {data}</div>;
}

// Slow component
async function SlowData() {
  const data = await fetch('https://api.example.com/slow');
  return <div>Slow: {data}</div>;
}

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Quick data renders immediately */}
      <Suspense fallback={<div>Loading quick data...</div>}>
        <QuickData />
      </Suspense>
      
      {/* Slow data streams in later */}
      <Suspense fallback={<div>Loading slow data...</div>}>
        <SlowData />
      </Suspense>
    </div>
  );
}

// Result: Page streams progressively
// 1. Shell with "Loading..." fallbacks sent immediately
// 2. QuickData arrives and replaces first fallback
// 3. SlowData arrives later and replaces second fallback

Example: Server-only code protection

// lib/database.ts
import 'server-only'; // Prevents client-side import

import { PrismaClient } from '@prisma/client';

// This will only run on server
export const db = new PrismaClient();

// Server-only utilities
export async function getSecretData() {
  // Safe to use environment variables
  const apiKey = process.env.SECRET_API_KEY;
  return fetchWithKey(apiKey);
}

// If you try to import this in a Client Component:
// 'use client';
// import { db } from '@/lib/database'; // ERROR at build time!

// lib/utils.ts - Shared utilities
// No 'server-only' import = can be used anywhere
export function formatDate(date) {
  return date.toLocaleDateString();
}

// Use 'server-only' package for:
// - Database clients
// - API clients with secrets
// - Server-only utilities
// - Code that must never be exposed to client
Data Fetching Best Practices: Fetch data at component level (not page level), use Suspense for streaming, parallelize independent requests, use request memoization (React dedupes), protect server-only code with 'server-only' package, cache appropriately with Next.js caching strategies.

16.4 Server Component Performance Benefits

Benefit Impact Measurement Example
Reduced Bundle Size Server code not sent to client Smaller JavaScript payloads 100KB library → 0KB on client
Faster Initial Load Less JavaScript to parse/execute Better Time to Interactive (TTI) Heavy formatters, utilities stay on server
Improved FCP HTML streams immediately First Contentful Paint Static content shows instantly
Better SEO Fully rendered HTML for crawlers Complete content in initial HTML No waiting for client-side fetch
Reduced Waterfalls Fetch data on server in parallel Fewer round trips No client → server → database chain
Direct Backend Access No API routes needed Fewer HTTP requests Direct database queries

Example: Bundle size comparison

// ❌ Client Component approach (old way)
'use client';
import moment from 'moment'; // 67KB gzipped!
import { remark } from 'remark'; // 50KB gzipped!

export function BlogPost({ content, date }) {
  const formattedDate = moment(date).format('MMMM Do, YYYY');
  const html = remark().processSync(content);
  
  return (
    <article>
      <time>{formattedDate}</time>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </article>
  );
}
// Total bundle increase: ~117KB just for this component!

// ✅ Server Component approach (new way)
// No 'use client' = Server Component
import moment from 'moment'; // Runs on server only
import { remark } from 'remark'; // Runs on server only

export async function BlogPost({ postId }) {
  const post = await db.post.findUnique({ where: { id: postId } });
  
  const formattedDate = moment(post.date).format('MMMM Do, YYYY');
  const html = remark().processSync(post.content);
  
  return (
    <article>
      <time>{formattedDate}</time>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </article>
  );
}
// Client bundle increase: 0KB! All processing on server.

Example: Performance optimization strategies

// Optimize with strategic Client/Server split

// Server Component - heavy processing
async function ProductGrid() {
  const products = await db.product.findMany({
    include: { reviews: true }
  });
  
  // Heavy computation on server
  const productsWithRatings = products.map(product => ({
    ...product,
    averageRating: calculateAverage(product.reviews),
    reviewCount: product.reviews.length
  }));
  
  return (
    <div className="grid">
      {productsWithRatings.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

// Client Component - only interactive parts
'use client';

export function ProductCard({ product }) {
  const [isFavorite, setIsFavorite] = useState(false);
  
  return (
    <div className="card">
      {/* Static content from server */}
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <div>⭐ {product.averageRating} ({product.reviewCount})</div>
      
      {/* Interactive part on client */}
      <button onClick={() => setIsFavorite(!isFavorite)}>
        {isFavorite ? '❤️' : '🤍'} Favorite
      </button>
    </div>
  );
}

// Benefits:
// ✓ Heavy computation (calculateAverage) runs on server
// ✓ Database query stays on server
// ✓ Only small interactive component sent to client
// ✓ Better performance overall
Performance Impact: Server Components significantly reduce bundle size, improve initial load times, enable better caching strategies, reduce network waterfalls, improve Core Web Vitals (LCP, FCP, TTI), benefit SEO with complete HTML.

16.5 Hydration and Client-side State Management

Concept Description Consideration Solution
Selective Hydration Hydrate client components only Server component HTML stays static Faster hydration, less JavaScript execution
State Management Client components manage state Server components are stateless Use Context, Zustand, Redux in client components
Server → Client Data Pass data via props Props must be serializable JSON-serializable data only
Client State Hydration Initialize client state from server data Avoid hydration mismatches Use useEffect for client-only state
Progressive Enhancement Works without JavaScript first Server Components always work Add interactivity with Client Components

Example: State management patterns

// Server Component fetches data
async function UserDashboard({ userId }) {
  const user = await db.user.findUnique({
    where: { id: userId },
    include: { preferences: true }
  });
  
  // Pass data to Client Component
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <UserSettings initialSettings={user.preferences} />
    </div>
  );
}

// Client Component manages state
'use client';

export function UserSettings({ initialSettings }) {
  // Initialize state from server data
  const [settings, setSettings] = useState(initialSettings);
  const [isSaving, setIsSaving] = useState(false);
  
  const handleSave = async () => {
    setIsSaving(true);
    await fetch('/api/settings', {
      method: 'POST',
      body: JSON.stringify(settings)
    });
    setIsSaving(false);
  };
  
  return (
    <div>
      <label>
        Theme:
        <select 
          value={settings.theme}
          onChange={(e) => setSettings({...settings, theme: e.target.value})}
        >
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </select>
      </label>
      
      <button onClick={handleSave} disabled={isSaving}>
        {isSaving ? 'Saving...' : 'Save Settings'}
      </button>
    </div>
  );
}

Example: Context providers in Client Components

// app/layout.tsx - Server Component
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {/* Providers must be Client Component */}
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  );
}

// app/providers.tsx - Client Component
'use client';

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

const ThemeContext = createContext();

export function Providers({ children }) {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}

// Any Client Component can now use theme
'use client';
import { useTheme } from './providers';

export function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current: {theme}
    </button>
  );
}

Example: Avoiding hydration mismatches

// ❌ WRONG: Causes hydration mismatch
'use client';

export function TimeDisplay() {
  // Server renders one time, client hydrates with different time
  return <div>Current time: {new Date().toLocaleTimeString()}</div>;
  // ERROR: Server HTML doesn't match client hydration
}

// ✅ CORRECT: Client-only rendering
'use client';

export function TimeDisplay() {
  const [time, setTime] = useState(null);
  
  useEffect(() => {
    // Only runs on client
    setTime(new Date().toLocaleTimeString());
    
    const interval = setInterval(() => {
      setTime(new Date().toLocaleTimeString());
    }, 1000);
    
    return () => clearInterval(interval);
  }, []);
  
  // Show nothing during SSR, then show time on client
  if (!time) return <div>Loading time...</div>;
  
  return <div>Current time: {time}</div>;
}

// ✅ ALTERNATIVE: Suppress hydration warning (use sparingly)
'use client';

export function TimeDisplay() {
  return (
    <div suppressHydrationWarning>
      Current time: {new Date().toLocaleTimeString()}
    </div>
  );
}
Hydration Warning: Client and server HTML must match during hydration. Use useEffect for client-only code, avoid Date.now() or random values in render, use suppressHydrationWarning sparingly, test hydration carefully.

16.6 Next.js App Router and Server Components

Feature File Convention Purpose Component Type
Page app/page.tsx Route page component Server by default
Layout app/layout.tsx Shared layout wrapper Server by default
Loading app/loading.tsx Loading UI (Suspense fallback) Server by default
Error app/error.tsx Error boundary UI Must be Client ('use client')
Template app/template.tsx Re-rendered on navigation Server by default
Route Handler app/api/route.ts API endpoint Server-only

Example: Next.js App Router file structure

app/
├── layout.tsx        # Root layout (Server Component)
├── page.tsx          # Home page (Server Component)
├── loading.tsx       # Loading UI (Server Component)
├── error.tsx         # Error boundary (Client Component)
├── providers.tsx     # Context providers (Client Component)

├── dashboard/
│   ├── layout.tsx    # Dashboard layout
│   ├── page.tsx      # Dashboard page
│   └── loading.tsx   # Dashboard loading

├── blog/
│   ├── page.tsx      # Blog list
│   └── [slug]/
│       ├── page.tsx  # Blog post (dynamic route)
│       └── loading.tsx

└── api/
    └── users/
        └── route.ts  # API route handler

// app/layout.tsx - Root Layout
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header>
          <nav>...</nav>
        </header>
        <main>{children}</main>
        <footer>...</footer>
      </body>
    </html>
  );
}

// app/page.tsx - Home Page (Server Component)
export default async function HomePage() {
  const posts = await db.post.findMany({ take: 5 });
  
  return (
    <div>
      <h1>Latest Posts</h1>
      <PostList posts={posts} />
    </div>
  );
}

// app/loading.tsx - Loading UI
export default function Loading() {
  return <div>Loading...</div>;
}

// app/error.tsx - Error Boundary (must be Client)
'use client';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Example: Dynamic routes and data fetching

// app/blog/[slug]/page.tsx - Dynamic route
export default async function BlogPost({ params }) {
  // Params are automatically typed and available
  const post = await db.post.findUnique({
    where: { slug: params.slug }
  });
  
  if (!post) {
    notFound(); // Shows 404 page
  }
  
  return (
    <article>
      <h1>{post.title}</h1>
      <time>{post.publishedAt}</time>
      <div>{post.content}</div>
    </article>
  );
}

// Generate static params for SSG
export async function generateStaticParams() {
  const posts = await db.post.findMany();
  
  return posts.map(post => ({
    slug: post.slug
  }));
}

// Generate metadata
export async function generateMetadata({ params }) {
  const post = await db.post.findUnique({
    where: { slug: params.slug }
  });
  
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage]
    }
  };
}

Example: Route handlers (API routes)

// app/api/users/route.ts - API Route Handler
import { NextResponse } from 'next/server';

// GET /api/users
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const page = searchParams.get('page') || '1';
  
  const users = await db.user.findMany({
    skip: (parseInt(page) - 1) * 10,
    take: 10
  });
  
  return NextResponse.json(users);
}

// POST /api/users
export async function POST(request) {
  const body = await request.json();
  
  const user = await db.user.create({
    data: body
  });
  
  return NextResponse.json(user, { status: 201 });
}

// app/api/users/[id]/route.ts - Dynamic route
export async function GET(request, { params }) {
  const user = await db.user.findUnique({
    where: { id: params.id }
  });
  
  if (!user) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }
  
  return NextResponse.json(user);
}

// PATCH /api/users/[id]
export async function PATCH(request, { params }) {
  const body = await request.json();
  
  const user = await db.user.update({
    where: { id: params.id },
    data: body
  });
  
  return NextResponse.json(user);
}

// DELETE /api/users/[id]
export async function DELETE(request, { params }) {
  await db.user.delete({
    where: { id: params.id }
  });
  
  return NextResponse.json({ success: true });
}
Next.js App Router Features: File-based routing, Server Components by default, automatic code splitting, built-in loading states, error boundaries, metadata API, route handlers for APIs, streaming with Suspense, TypeScript support.

Server Components and Full-Stack React Best Practices

17. React Testing Strategies and Patterns

17.1 React Testing Library Best Practices

Practice Description Example Rationale
Test User Behavior Test components as users interact with them, not implementation details screen.getByRole('button') Tests are resilient to refactoring and reflect real usage
Query Priority Use accessible queries first: getByRole, getByLabelText, getByPlaceholderText, getByText getByRole('textbox', {name: /email/i}) Ensures accessibility and semantic HTML
Avoid Test IDs Use data-testid only as last resort when no semantic query available getByTestId('custom-widget') Encourages better accessibility practices
User-Event Library Use @testing-library/user-event for realistic interactions await user.click(button) Simulates real browser events more accurately than fireEvent
Async Utilities Use waitFor, findBy queries for async operations await screen.findByText('Loaded') Handles async updates and avoids race conditions
Avoid act() Warnings Use RTL's async utilities instead of manual act() wrapping await waitFor(() => expect(...)) Built-in utilities handle act() automatically

Example: Basic RTL test with best practices

// UserGreeting.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserGreeting from './UserGreeting';

describe('UserGreeting', () => {
  it('displays greeting after user enters name', async () => {
    const user = userEvent.setup();
    render(<UserGreeting />);
    
    // Use accessible query
    const input = screen.getByRole('textbox', { name: /your name/i });
    const button = screen.getByRole('button', { name: /submit/i });
    
    // Simulate user interaction
    await user.type(input, 'Alice');
    await user.click(button);
    
    // Assert user-visible outcome
    expect(await screen.findByText(/hello, alice/i)).toBeInTheDocument();
  });
});

Example: Query priority guide

// Preferred: Accessible to everyone
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText(/email address/i)
screen.getByPlaceholderText(/search.../i)
screen.getByText(/welcome back/i)

// Semantic queries
screen.getByAltText(/company logo/i)
screen.getByTitle(/close dialog/i)

// Test IDs (last resort)
screen.getByTestId('custom-element')

17.2 Component Testing and User Interactions

Test Type Focus Query Method Example
Form Input User typing and form submission user.type(), user.click() Type into input, submit form, verify result
Button Clicks Click handlers and state changes user.click() Click button, verify UI update or API call
Async Data Loading Fetch and display data findBy*, waitFor() Wait for loading state, then verify data displayed
Conditional Rendering Show/hide elements based on state queryBy*, expect().not.toBeInTheDocument() Verify element appears/disappears correctly
Keyboard Navigation Tab, Enter, Escape key handling user.keyboard(), user.tab() Navigate with keyboard, verify focus management
Selection Controls Radio, checkbox, select interactions user.selectOptions(), user.click() Select options, verify state and behavior

Example: Testing form with validation

// LoginForm.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';

describe('LoginForm', () => {
  it('shows validation errors for invalid inputs', async () => {
    const user = userEvent.setup();
    const mockSubmit = jest.fn();
    render(<LoginForm onSubmit={mockSubmit} />);
    
    const emailInput = screen.getByLabelText(/email/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const submitButton = screen.getByRole('button', { name: /log in/i });
    
    // Submit with invalid email
    await user.type(emailInput, 'invalid-email');
    await user.type(passwordInput, '123');
    await user.click(submitButton);
    
    // Verify validation errors appear
    expect(await screen.findByText(/invalid email format/i)).toBeInTheDocument();
    expect(await screen.findByText(/password must be at least 8 characters/i)).toBeInTheDocument();
    expect(mockSubmit).not.toHaveBeenCalled();
  });

  it('submits form with valid inputs', async () => {
    const user = userEvent.setup();
    const mockSubmit = jest.fn();
    render(<LoginForm onSubmit={mockSubmit} />);
    
    await user.type(screen.getByLabelText(/email/i), 'user@example.com');
    await user.type(screen.getByLabelText(/password/i), 'securePassword123');
    await user.click(screen.getByRole('button', { name: /log in/i }));
    
    expect(mockSubmit).toHaveBeenCalledWith({
      email: 'user@example.com',
      password: 'securePassword123'
    });
  });
});

Example: Testing async data fetching

// UserProfile.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
import { fetchUser } from './api';

jest.mock('./api');

describe('UserProfile', () => {
  it('displays loading state then user data', async () => {
    fetchUser.mockResolvedValue({
      id: 1,
      name: 'Alice',
      email: 'alice@example.com'
    });
    
    render(<UserProfile userId={1} />);
    
    // Verify loading state
    expect(screen.getByText(/loading.../i)).toBeInTheDocument();
    
    // Wait for data to load
    expect(await screen.findByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('alice@example.com')).toBeInTheDocument();
    expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
  });

  it('displays error message on fetch failure', async () => {
    fetchUser.mockRejectedValue(new Error('Network error'));
    
    render(<UserProfile userId={1} />);
    
    expect(await screen.findByText(/failed to load user/i)).toBeInTheDocument();
  });
});

17.3 Hook Testing with renderHook

Hook Type Testing Approach Key Method Example
Custom Hooks Test hook logic in isolation renderHook() Test custom hook without wrapping component
State Updates Test state changes and side effects result.current, act() Access hook values and trigger updates
Async Hooks Test async operations in hooks waitFor(() => expect(...)) Wait for async state updates
Hook Dependencies Test effect dependencies and re-runs rerender() Change props and verify effect behavior
Context Hooks Test hooks requiring context wrapper: Provider Wrap hook with necessary providers
Cleanup Testing Verify cleanup functions run unmount() Test effect cleanup and subscriptions

Example: Testing custom hook with state

// useCounter.test.ts
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';

describe('useCounter', () => {
  it('initializes with default value', () => {
    const { result } = renderHook(() => useCounter());
    expect(result.current.count).toBe(0);
  });

  it('initializes with custom value', () => {
    const { result } = renderHook(() => useCounter(10));
    expect(result.current.count).toBe(10);
  });

  it('increments count', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });

  it('decrements count', () => {
    const { result } = renderHook(() => useCounter(5));
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(4);
  });

  it('resets count to initial value', () => {
    const { result } = renderHook(() => useCounter(10));
    
    act(() => {
      result.current.increment();
      result.current.increment();
      result.current.reset();
    });
    
    expect(result.current.count).toBe(10);
  });
});

Example: Testing async hook with API calls

// useFetchUser.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import useFetchUser from './useFetchUser';
import { fetchUser } from './api';

jest.mock('./api');

describe('useFetchUser', () => {
  it('fetches user data successfully', async () => {
    const mockUser = { id: 1, name: 'Alice' };
    fetchUser.mockResolvedValue(mockUser);
    
    const { result } = renderHook(() => useFetchUser(1));
    
    // Initial state
    expect(result.current.loading).toBe(true);
    expect(result.current.data).toBeNull();
    expect(result.current.error).toBeNull();
    
    // Wait for data to load
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });
    
    expect(result.current.data).toEqual(mockUser);
    expect(result.current.error).toBeNull();
  });

  it('handles fetch errors', async () => {
    fetchUser.mockRejectedValue(new Error('Network error'));
    
    const { result } = renderHook(() => useFetchUser(1));
    
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });
    
    expect(result.current.data).toBeNull();
    expect(result.current.error).toEqual(new Error('Network error'));
  });

  it('refetches when userId changes', async () => {
    fetchUser.mockResolvedValue({ id: 1, name: 'Alice' });
    
    const { result, rerender } = renderHook(
      ({ userId }) => useFetchUser(userId),
      { initialProps: { userId: 1 } }
    );
    
    await waitFor(() => expect(result.current.loading).toBe(false));
    expect(fetchUser).toHaveBeenCalledWith(1);
    
    // Change userId
    fetchUser.mockResolvedValue({ id: 2, name: 'Bob' });
    rerender({ userId: 2 });
    
    await waitFor(() => expect(result.current.data.name).toBe('Bob'));
    expect(fetchUser).toHaveBeenCalledWith(2);
  });
});

Example: Testing hook with context

// useAuth.test.tsx
import { renderHook } from '@testing-library/react';
import { AuthProvider } from './AuthContext';
import useAuth from './useAuth';

const wrapper = ({ children }) => (
  <AuthProvider initialUser={{ id: 1, name: 'Alice' }}>
    {children}
  </AuthProvider>
);

describe('useAuth', () => {
  it('provides auth context values', () => {
    const { result } = renderHook(() => useAuth(), { wrapper });
    
    expect(result.current.user).toEqual({ id: 1, name: 'Alice' });
    expect(result.current.isAuthenticated).toBe(true);
  });

  it('throws error when used outside provider', () => {
    // Suppress console.error for this test
    const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
    
    expect(() => {
      renderHook(() => useAuth());
    }).toThrow('useAuth must be used within AuthProvider');
    
    spy.mockRestore();
  });
});

17.4 Mocking and Test Doubles for Components

Mock Type Use Case Implementation Example
Module Mocking Replace entire module with mock jest.mock('./module') Mock API clients, utilities
Function Mocking Mock specific functions jest.fn() Mock callbacks, event handlers
Component Mocking Replace child components jest.mock('./Component') Isolate component under test
Spy Functions Track function calls without replacing jest.spyOn(obj, 'method') Verify method calls on objects
Timer Mocking Control setTimeout, setInterval jest.useFakeTimers() Test debounce, throttle, delays
Network Mocking Mock HTTP requests MSW (Mock Service Worker) Mock fetch, axios requests

Example: Mocking API calls

// TodoList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TodoList from './TodoList';
import * as api from './api';

// Mock the entire API module
jest.mock('./api');

describe('TodoList', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('loads and displays todos', async () => {
    const mockTodos = [
      { id: 1, title: 'Buy milk', completed: false },
      { id: 2, title: 'Walk dog', completed: true }
    ];
    
    api.fetchTodos.mockResolvedValue(mockTodos);
    
    render(<TodoList />);
    
    expect(await screen.findByText('Buy milk')).toBeInTheDocument();
    expect(screen.getByText('Walk dog')).toBeInTheDocument();
    expect(api.fetchTodos).toHaveBeenCalledTimes(1);
  });

  it('adds new todo', async () => {
    const user = userEvent.setup();
    api.fetchTodos.mockResolvedValue([]);
    api.createTodo.mockResolvedValue({ id: 3, title: 'New task', completed: false });
    
    render(<TodoList />);
    
    const input = screen.getByPlaceholderText(/new todo/i);
    await user.type(input, 'New task');
    await user.click(screen.getByRole('button', { name: /add/i }));
    
    await waitFor(() => {
      expect(api.createTodo).toHaveBeenCalledWith({ title: 'New task' });
    });
    
    expect(await screen.findByText('New task')).toBeInTheDocument();
  });
});

Example: Mocking child components

// Dashboard.test.tsx
import { render, screen } from '@testing-library/react';
import Dashboard from './Dashboard';

// Mock complex child components
jest.mock('./UserProfile', () => {
  return function MockUserProfile({ userId }) {
    return <div>User Profile: {userId}</div>;
  };
});

jest.mock('./ActivityFeed', () => {
  return function MockActivityFeed() {
    return <div>Activity Feed</div>;
  };
});

describe('Dashboard', () => {
  it('renders main layout with mocked children', () => {
    render(<Dashboard userId={123} />);
    
    expect(screen.getByText('User Profile: 123')).toBeInTheDocument();
    expect(screen.getByText('Activity Feed')).toBeInTheDocument();
  });
});

Example: Using Mock Service Worker (MSW)

// setupTests.ts
import { setupServer } from 'msw/node';
import { rest } from 'msw';

const handlers = [
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' }
      ])
    );
  }),
];

export const server = setupServer(...handlers);

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

// UserList.test.tsx
import { render, screen } from '@testing-library/react';
import { server } from './setupTests';
import { rest } from 'msw';
import UserList from './UserList';

describe('UserList', () => {
  it('displays users from API', async () => {
    render(<UserList />);
    
    expect(await screen.findByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('Bob')).toBeInTheDocument();
  });

  it('handles API errors', async () => {
    // Override handler for this test
    server.use(
      rest.get('/api/users', (req, res, ctx) => {
        return res(ctx.status(500), ctx.json({ error: 'Server error' }));
      })
    );
    
    render(<UserList />);
    
    expect(await screen.findByText(/failed to load users/i)).toBeInTheDocument();
  });
});

Example: Testing with fake timers

// SearchInput.test.tsx - testing debounced input
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import SearchInput from './SearchInput';

describe('SearchInput with debounce', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.runOnlyPendingTimers();
    jest.useRealTimers();
  });

  it('debounces search callback', async () => {
    const mockSearch = jest.fn();
    const user = userEvent.setup({ delay: null }); // Disable userEvent delays
    
    render(<SearchInput onSearch={mockSearch} debounceMs={500} />);
    
    const input = screen.getByRole('textbox');
    
    // Type quickly
    await user.type(input, 'test');
    
    // Callback not called yet
    expect(mockSearch).not.toHaveBeenCalled();
    
    // Fast-forward time
    jest.advanceTimersByTime(500);
    
    // Now callback is called once with final value
    expect(mockSearch).toHaveBeenCalledTimes(1);
    expect(mockSearch).toHaveBeenCalledWith('test');
  });
});

17.5 Integration Testing with React Router

Test Scenario Setup Key Utilities Example
Route Rendering Wrap with MemoryRouter MemoryRouter Test component renders at specific route
Navigation Simulate link clicks user.click(), screen queries Click link, verify new route content
Route Parameters Initialize with initialEntries initialEntries: ['/user/123'] Test dynamic route params
Protected Routes Test auth redirects Navigate, conditional rendering Verify redirect when unauthorized
History Manipulation Create test history createMemoryHistory() Test back/forward navigation
Search Params Include query strings initialEntries: ['?tab=2'] Test URL search params handling

Example: Testing navigation and routes

// App.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import App from './App';

describe('App Navigation', () => {
  it('navigates from home to about page', async () => {
    const user = userEvent.setup();
    
    render(
      <MemoryRouter initialEntries={['/']}>
        <App />
      </MemoryRouter>
    );
    
    // Verify home page
    expect(screen.getByText(/welcome home/i)).toBeInTheDocument();
    
    // Click about link
    const aboutLink = screen.getByRole('link', { name: /about/i });
    await user.click(aboutLink);
    
    // Verify about page
    expect(screen.getByText(/about us/i)).toBeInTheDocument();
    expect(screen.queryByText(/welcome home/i)).not.toBeInTheDocument();
  });

  it('renders 404 page for unknown routes', () => {
    render(
      <MemoryRouter initialEntries={['/unknown-route']}>
        <App />
      </MemoryRouter>
    );
    
    expect(screen.getByText(/404.*not found/i)).toBeInTheDocument();
  });
});

Example: Testing dynamic routes with params

// UserProfile.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import UserProfile from './UserProfile';
import * as api from './api';

jest.mock('./api');

describe('UserProfile', () => {
  it('loads user data based on route param', async () => {
    const mockUser = { id: '123', name: 'Alice', email: 'alice@example.com' };
    api.fetchUser.mockResolvedValue(mockUser);
    
    render(
      <MemoryRouter initialEntries={['/user/123']}>
        <Routes>
          <Route path="/user/:userId" element={<UserProfile />} />
        </Routes>
      </MemoryRouter>
    );
    
    await waitFor(() => {
      expect(api.fetchUser).toHaveBeenCalledWith('123');
    });
    
    expect(await screen.findByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('alice@example.com')).toBeInTheDocument();
  });
});

Example: Testing protected routes

// ProtectedRoute.test.tsx
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
import { AuthProvider } from './AuthContext';

describe('Protected Routes', () => {
  it('redirects to login when not authenticated', () => {
    render(
      <MemoryRouter initialEntries={['/dashboard']}>
        <AuthProvider initialAuth={{ user: null, isAuthenticated: false }}>
          <App />
        </AuthProvider>
      </MemoryRouter>
    );
    
    // Should be redirected to login
    expect(screen.getByText(/please log in/i)).toBeInTheDocument();
    expect(screen.queryByText(/dashboard/i)).not.toBeInTheDocument();
  });

  it('allows access when authenticated', () => {
    render(
      <MemoryRouter initialEntries={['/dashboard']}>
        <AuthProvider initialAuth={{ 
          user: { id: 1, name: 'Alice' }, 
          isAuthenticated: true 
        }}>
          <App />
        </AuthProvider>
      </MemoryRouter>
    );
    
    // Should show dashboard
    expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
    expect(screen.queryByText(/please log in/i)).not.toBeInTheDocument();
  });
});

17.6 End-to-End Testing with Cypress/Playwright

Tool Strengths Test Pattern Use Case
Cypress Developer-friendly, time-travel debugging, real-time reloading cy.visit(), cy.get(), cy.click() Full user flows, visual testing, network mocking
Playwright Multi-browser, parallel execution, mobile emulation, auto-wait page.goto(), page.click(), page.fill() Cross-browser testing, mobile testing, API testing
Cypress Commands Custom reusable commands Cypress.Commands.add() Login flows, common interactions
Network Interception Mock/stub API responses cy.intercept(), page.route() Test error states, loading states
Visual Testing Screenshot comparison cy.screenshot(), expect(screenshot) Detect visual regressions
Component Testing Mount React components in test runner cy.mount(), mount() Component isolation with real browser

Example: Cypress E2E test

// cypress/e2e/login.cy.js
describe('Login Flow', () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000');
  });

  it('allows user to log in with valid credentials', () => {
    // Intercept API call
    cy.intercept('POST', '/api/login', {
      statusCode: 200,
      body: {
        user: { id: 1, name: 'Alice', email: 'alice@example.com' },
        token: 'fake-jwt-token'
      }
    }).as('loginRequest');

    // Fill login form
    cy.get('input[name="email"]').type('alice@example.com');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();

    // Wait for API call
    cy.wait('@loginRequest');

    // Verify redirect to dashboard
    cy.url().should('include', '/dashboard');
    cy.contains('Welcome, Alice').should('be.visible');
    
    // Verify token stored
    cy.window().its('localStorage.token').should('exist');
  });

  it('shows error message for invalid credentials', () => {
    cy.intercept('POST', '/api/login', {
      statusCode: 401,
      body: { error: 'Invalid credentials' }
    }).as('loginRequest');

    cy.get('input[name="email"]').type('wrong@example.com');
    cy.get('input[name="password"]').type('wrongpassword');
    cy.get('button[type="submit"]').click();

    cy.wait('@loginRequest');

    // Verify error message
    cy.contains('Invalid credentials').should('be.visible');
    cy.url().should('include', '/login');
  });

  it('validates form inputs', () => {
    cy.get('button[type="submit"]').click();

    // Check validation errors
    cy.contains('Email is required').should('be.visible');
    cy.contains('Password is required').should('be.visible');

    // Fill invalid email
    cy.get('input[name="email"]').type('invalid-email');
    cy.get('button[type="submit"]').click();
    cy.contains('Invalid email format').should('be.visible');
  });
});

Example: Cypress custom commands

// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login');
  cy.get('input[name="email"]').type(email);
  cy.get('input[name="password"]').type(password);
  cy.get('button[type="submit"]').click();
  cy.url().should('include', '/dashboard');
});

Cypress.Commands.add('logout', () => {
  cy.get('[data-testid="user-menu"]').click();
  cy.contains('Logout').click();
  cy.url().should('include', '/login');
});

// Usage in tests
describe('Dashboard', () => {
  beforeEach(() => {
    cy.login('alice@example.com', 'password123');
  });

  it('displays user data', () => {
    cy.contains('Welcome, Alice').should('be.visible');
  });

  it('allows user to logout', () => {
    cy.logout();
  });
});

Example: Playwright E2E test

// tests/login.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000');
  });

  test('allows user to log in with valid credentials', async ({ page }) => {
    // Mock API response
    await page.route('**/api/login', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          user: { id: 1, name: 'Alice', email: 'alice@example.com' },
          token: 'fake-jwt-token'
        })
      });
    });

    // Fill and submit form
    await page.fill('input[name="email"]', 'alice@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');

    // Verify redirect
    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.locator('text=Welcome, Alice')).toBeVisible();
  });

  test('handles network errors gracefully', async ({ page }) => {
    // Simulate network failure
    await page.route('**/api/login', (route) => route.abort());

    await page.fill('input[name="email"]', 'alice@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');

    // Verify error message
    await expect(page.locator('text=/network error/i')).toBeVisible();
  });
});

Example: Playwright with fixtures and page objects

// tests/fixtures.ts
import { test as base } from '@playwright/test';

// Create custom fixture for authenticated user
export const test = base.extend({
  authenticatedPage: async ({ page }, use) => {
    await page.goto('http://localhost:3000/login');
    await page.fill('input[name="email"]', 'alice@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    await page.waitForURL('**/dashboard');
    await use(page);
  }
});

// tests/pages/DashboardPage.ts
export class DashboardPage {
  constructor(private page) {}

  async navigateToSettings() {
    await this.page.click('[data-testid="settings-link"]');
  }

  async getUserName() {
    return await this.page.locator('[data-testid="user-name"]').textContent();
  }

  async createNewProject(name: string) {
    await this.page.click('button:has-text("New Project")');
    await this.page.fill('input[name="project-name"]', name);
    await this.page.click('button:has-text("Create")');
  }
}

// tests/dashboard.spec.ts
import { test } from './fixtures';
import { DashboardPage } from './pages/DashboardPage';
import { expect } from '@playwright/test';

test('user can create new project', async ({ authenticatedPage }) => {
  const dashboard = new DashboardPage(authenticatedPage);
  
  await dashboard.createNewProject('My New Project');
  
  await expect(authenticatedPage.locator('text=My New Project')).toBeVisible();
});
E2E Testing Best Practices: Start server before tests, use baseURL configuration, avoid hard-coded waits (use auto-waiting), test critical user paths only, run in CI/CD pipeline, use parallel execution, implement proper cleanup, mock external services, use data-testid for stable selectors, organize tests by feature, maintain test independence.

React Testing Best Practices Summary

18. React Developer Tools and Debugging

18.1 React DevTools Browser Extension

Feature Purpose Access Method Key Capabilities
Components Tab Inspect component tree and props/state Browser DevTools → React tab View hierarchy, search components, inspect props/state/hooks
Profiler Tab Measure rendering performance Browser DevTools → Profiler tab Record renders, flame graphs, ranked charts, commit details
Settings Configure DevTools behavior Gear icon in DevTools Highlight updates, trace updates, component filters, theme
Highlight Updates Visually show component re-renders Settings → Highlight updates Color-coded borders around re-rendering components
Strict Mode Badge Indicates Strict Mode enabled Component tree icons Shows which components wrapped in StrictMode
Component Filters Hide unwanted components from tree Settings → Component filters Filter by name, location, or HOC patterns

Example: Installing React DevTools

// Chrome Web Store
https://chrome.google.com/webstore (search "React Developer Tools")

// Firefox Add-ons
https://addons.mozilla.org (search "React Developer Tools")

// Edge Add-ons
https://microsoftedge.microsoft.com/addons (search "React Developer Tools")

// Standalone (Electron app)
npm install -g react-devtools
react-devtools

// Connect standalone to app
<script src="http://localhost:8097"></script>

Example: Accessing DevTools features

// Enable highlight updates to see re-renders
// 1. Open React DevTools
// 2. Click Settings (gear icon)
// 3. Check "Highlight updates when components render"
// 4. Watch colored borders appear on updates

// Search for components
// 1. Click in Components tab
// 2. Press Cmd+F (Mac) or Ctrl+F (Windows)
// 3. Type component name
// 4. Navigate with Enter/Shift+Enter

// View component source
// 1. Select component in tree
// 2. Right panel shows props, state, hooks
// 3. Click "<>" icon to view source code
// 4. Opens in Sources tab at component definition
DevTools Shortcuts: Cmd/Ctrl + F (search), Cmd/Ctrl + P (search by component name), Click component in page to select in tree (requires "Inspect" feature enabled), Click eye icon to inspect DOM node, Use filter input to narrow tree view.

18.2 Component Inspector and Props Debugging

Inspector Feature Information Shown Interaction Use Case
Props Panel All props passed to component View values, edit primitives Debug incorrect prop values, test different prop combinations
State Panel Component state values View and edit state Test different states, reproduce bugs, validate state logic
Hooks Panel All hooks in component with values View hook state, effects Debug custom hooks, verify hook dependencies
Rendered By Parent component that rendered this component Click to navigate to parent Understand component hierarchy and data flow
Owners Component ownership chain Shows entire owner hierarchy Trace where props originated, debug context issues
Source Location File and line number of component Click to open in Sources tab Quick navigation to component code

Example: Editing props and state in DevTools

// Original component
function Counter() {
  const [count, setCount] = useState(0);
  return <div>Count: {count}</div>;
}

// In React DevTools:
// 1. Select Counter component
// 2. In right panel, see "State" section
// 3. Click on "count: 0" value
// 4. Edit to different value (e.g., 100)
// 5. Press Enter - component re-renders with new value

// Editing props (parent component needed)
function Parent() {
  return <Child name="Alice" age={25} />;
}

// In DevTools:
// 1. Select Child component
// 2. See "Props" section: name: "Alice", age: 25
// 3. Edit primitive values directly
// 4. Note: Complex objects/functions can't be edited

Example: Debugging hooks with DevTools

function UserProfile() {
  const [loading, setLoading] = useState(true);
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetchUser().then(setUser).catch(setError).finally(() => setLoading(false));
  }, []);
  
  const memoizedUser = useMemo(() => formatUser(user), [user]);
  const handleClick = useCallback(() => console.log(user), [user]);
  
  // ...
}

// In DevTools, Hooks section shows:
// State (0): true          // loading
// State (1): null          // user
// State (2): null          // error
// Effect                   // useEffect
// Memo                     // useMemo
// Callback                 // useCallback

// You can:
// - Edit state values directly
// - See dependencies for useMemo/useCallback
// - Track which effects ran
// - Identify custom hooks by name

Example: Using "Rendered by" to trace component tree

// Component hierarchy
<App>
  <Dashboard>
    <UserPanel>
      <UserAvatar src={user.avatar} />
    </UserPanel>
  </Dashboard>
</App>

// Select UserAvatar in DevTools
// "Rendered by" section shows:
// UserPanel → Dashboard → App

// Click any parent to navigate
// See full props chain:
// - App passes user to Dashboard
// - Dashboard passes user to UserPanel
// - UserPanel passes user.avatar to UserAvatar

18.3 Profiler and Performance Analysis

Profiler Feature Visualization Metrics Analysis Use
Flamegraph Chart Hierarchical view of render times Render duration per component Identify slow components, bottlenecks in tree
Ranked Chart Sorted list of components by render time Duration, sorted slowest first Quickly find most expensive renders
Component Chart Timeline of individual component renders Each commit, duration, props/state changes Track re-render frequency, identify unnecessary renders
Commit Details Specific render commit information What triggered render, duration, timestamp Understand why component re-rendered
Interactions User interactions that triggered renders Which user actions caused updates Measure interaction responsiveness
Why Did This Render Explanation of render cause Props changed, state changed, parent rendered Debug unnecessary re-renders

Example: Using Profiler to identify performance issues

// Steps to profile your app:
// 1. Open React DevTools → Profiler tab
// 2. Click "Start Profiling" (blue record button)
// 3. Interact with your app (click buttons, type, navigate)
// 4. Click "Stop Profiling" (red square button)
// 5. Analyze the results

// Reading Flamegraph:
// - Wider bars = longer render time
// - Height = component depth in tree
// - Colors: Yellow (fast), Orange (medium), Red (slow)
// - Click bar to see component details

// Reading Ranked Chart:
// - Components sorted by render duration
// - Percentage shows relative time
// - Focus on top components first

// Example findings:
// Component: BigList
// Rendered: 15 times
// Duration: 1,245ms total
// Cause: Parent state change (unnecessary)
// Fix: Wrap in React.memo()

Example: Profiler API for custom measurements

import { Profiler } from 'react';

function onRenderCallback(
  id,        // "panel" - profiler ID
  phase,     // "mount" or "update"
  actualDuration,  // Time spent rendering
  baseDuration,    // Estimated time without memoization
  startTime,       // When render started
  commitTime,      // When React committed update
  interactions     // Set of interactions
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
  
  // Send to analytics
  analytics.track('component-render', {
    component: id,
    phase,
    duration: actualDuration,
    wasSlower: actualDuration > baseDuration
  });
}

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

// Nested profilers for granular tracking
function Dashboard() {
  return (
    <div>
      <Profiler id="sidebar" onRender={onRenderCallback}>
        <Sidebar />
      </Profiler>
      <Profiler id="content" onRender={onRenderCallback}>
        <Content />
      </Profiler>
    </div>
  );
}

Example: Analyzing "Why Did This Render"

// Enable in Settings → General → "Record why each component rendered"

// Example analysis in DevTools:
// Component: TodoItem
// Rendered because: Props changed
// Changed props:
//   - isCompleted: false → true
//   - onClick: [function] → [function] (different reference!)

// Problem identified: onClick recreated on every render
// Before (bad):
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  return todos.map(todo => (
    <TodoItem
      todo={todo}
      onClick={() => handleToggle(todo.id)} // New function every render!
    />
  ));
}

// After (good):
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(t => 
      t.id === id ? { ...t, completed: !t.completed } : t
    ));
  }, []);
  
  return todos.map(todo => (
    <TodoItem
      key={todo.id}
      todo={todo}
      onToggle={handleToggle} // Stable reference
    />
  ));
}

const TodoItem = React.memo(({ todo, onToggle }) => (
  <li onClick={() => onToggle(todo.id)}>{todo.text}</li>
));

18.4 State and Context Debugging Techniques

Technique Tool/Method What to Check Common Issues Found
useState Inspection DevTools Hooks panel Current state values, update functions Stale closures, incorrect initial state, batching issues
useReducer Debugging DevTools + Redux DevTools State shape, action history Incorrect action handling, state mutations, missing cases
Context Values DevTools Context panel Current context value, provider location Missing provider, wrong value, too many re-renders
State History Custom logging, Redux DevTools State changes over time Unexpected mutations, missing updates
State Synchronization Multiple component inspection Shared state consistency Race conditions, stale data, caching issues
Derived State useMemo inspection Memoized values, dependencies Missing dependencies, over-computation

Example: Debugging context issues

// Context not updating problem
const UserContext = createContext();

function App() {
  const [user, setUser] = useState({ name: 'Alice', age: 25 });
  
  // Problem: New object every render!
  const value = { user, setUser };
  
  return (
    <UserContext.Provider value={value}>
      <Dashboard />
    </UserContext.Provider>
  );
}

// In DevTools:
// 1. Select any consumer component
// 2. Check "Context" in right panel
// 3. See value changing every render (object identity)
// 4. All consumers re-render unnecessarily

// Fix:
function App() {
  const [user, setUser] = useState({ name: 'Alice', age: 25 });
  
  const value = useMemo(() => ({ user, setUser }), [user]);
  
  return (
    <UserContext.Provider value={value}>
      <Dashboard />
    </UserContext.Provider>
  );
}

Example: Using Redux DevTools with useReducer

import { useReducer } from 'react';

// Install Redux DevTools Extension for full state tracking

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, action.payload];
    case 'REMOVE':
      return state.filter(todo => todo.id !== action.payload);
    case 'TOGGLE':
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
}

// Connect useReducer to Redux DevTools
function useReducerWithDevTools(reducer, initialState, name) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  useEffect(() => {
    if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
      const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
      devTools.init(initialState);
      
      const wrappedDispatch = (action) => {
        dispatch(action);
        devTools.send(action, reducer(state, action));
      };
      
      return () => devTools.disconnect();
    }
  }, []);
  
  return [state, dispatch];
}

// Usage
function TodoApp() {
  const [todos, dispatch] = useReducerWithDevTools(
    todoReducer,
    [],
    'TodoApp'
  );
  
  // Now see all actions in Redux DevTools!
  // - Time-travel debugging
  // - Action history
  // - State diff
}

Example: Custom state debugging hooks

// Development-only state logger
function useStateWithLogger(initialState, name) {
  const [state, setState] = useState(initialState);
  
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      console.log(`[${name}] State changed:`, state);
    }
  }, [state, name]);
  
  return [state, setState];
}

// Track state change reasons
function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef();
  
  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps = {};
      
      allKeys.forEach(key => {
        if (previousProps.current[key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current[key],
            to: props[key]
          };
        }
      });
      
      if (Object.keys(changedProps).length > 0) {
        console.log('[why-did-you-update]', name, changedProps);
      }
    }
    
    previousProps.current = props;
  });
}

// Usage
function ExpensiveComponent(props) {
  useWhyDidYouUpdate('ExpensiveComponent', props);
  return <div>...</div>;
}

18.5 Time-travel Debugging and Component History

Feature Capability Tool Use Case
Profiler Timeline Scrub through recorded renders React DevTools Profiler See component states at different render commits
Redux DevTools Jump to any action in history Redux DevTools Extension Time-travel through state changes, replay actions
React Query DevTools View cache history and mutations @tanstack/react-query-devtools Debug data fetching and caching issues
Custom History Track component lifecycle events Custom hooks with logging Create audit trail of state changes
Browser DevTools Record and replay interactions Chrome Recorder, Replay.io Reproduce bugs, share reproductions
Error Replay Record session before errors LogRocket, FullStory, Sentry Debug production errors with context

Example: Using Profiler for time-travel debugging

// Record interaction flow:
// 1. Open React DevTools → Profiler
// 2. Click Record
// 3. Perform user actions (form submit, navigation, etc.)
// 4. Click Stop
// 5. Use timeline scrubber to navigate commits

// Each commit shows:
// - Which components rendered
// - Why they rendered (props/state change)
// - Render duration
// - Component props/state at that moment

// Navigate commits:
// - Left/Right arrows to step through
// - Click specific commit in timeline
// - See exact component state at that point
// - Compare before/after states

Example: Redux DevTools time-travel

// Install Redux DevTools Extension

// Setup store with DevTools
import { createStore } from 'redux';

const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

// In Redux DevTools panel:
// 1. See all dispatched actions in order
// 2. Click any action to jump to that state
// 3. Use slider to scrub through action history
// 4. Toggle actions on/off to see impact
// 5. Export/import state snapshots
// 6. Replay actions at different speeds

// DevTools features:
// - Diff: See what changed in state
// - Test: Generate test code from actions
// - Export: Save state/action history as JSON
// - Import: Load previous session
// - Persist: Keep state across page reloads

Example: Building custom history tracker

// Create state history hook
function useStateHistory(initialState, maxHistory = 50) {
  const [state, setState] = useState(initialState);
  const [history, setHistory] = useState([initialState]);
  const [currentIndex, setCurrentIndex] = useState(0);
  
  const updateState = useCallback((newState) => {
    const value = typeof newState === 'function' ? newState(state) : newState;
    
    setHistory(prev => {
      const newHistory = prev.slice(0, currentIndex + 1);
      newHistory.push(value);
      return newHistory.slice(-maxHistory);
    });
    
    setCurrentIndex(prev => Math.min(prev + 1, maxHistory - 1));
    setState(value);
  }, [state, currentIndex, maxHistory]);
  
  const undo = useCallback(() => {
    if (currentIndex > 0) {
      const newIndex = currentIndex - 1;
      setCurrentIndex(newIndex);
      setState(history[newIndex]);
    }
  }, [currentIndex, history]);
  
  const redo = useCallback(() => {
    if (currentIndex < history.length - 1) {
      const newIndex = currentIndex + 1;
      setCurrentIndex(newIndex);
      setState(history[newIndex]);
    }
  }, [currentIndex, history]);
  
  const canUndo = currentIndex > 0;
  const canRedo = currentIndex < history.length - 1;
  
  return {
    state,
    setState: updateState,
    undo,
    redo,
    canUndo,
    canRedo,
    history,
    currentIndex
  };
}

// Usage - Text editor with undo/redo
function TextEditor() {
  const { 
    state: text, 
    setState: setText, 
    undo, 
    redo, 
    canUndo, 
    canRedo 
  } = useStateHistory('');
  
  return (
    <div>
      <button onClick={undo} disabled={!canUndo}>Undo</button>
      <button onClick={redo} disabled={!canRedo}>Redo</button>
      <textarea 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
      />
    </div>
  );
}

18.6 Production Debugging and Error Tracking

Tool/Service Capabilities Key Features Best For
Sentry Error tracking, performance monitoring Error grouping, source maps, breadcrumbs, releases Production error tracking and alerting
LogRocket Session replay, error tracking Video replay, network logs, console logs, Redux state Visual debugging with full session context
Bugsnag Error monitoring and reporting Stability score, release tracking, user impact analysis Error management and prioritization
Datadog RUM Real user monitoring Performance metrics, session replay, error tracking, analytics Full-stack monitoring and observability
React Error Boundary Catch React errors in production Fallback UI, error reporting integration Graceful error handling in app
Source Maps Map minified code to source Readable stack traces in production Debug minified production code

Example: Sentry setup for React

// Install
npm install @sentry/react

// Initialize in entry point
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  integrations: [
    new BrowserTracing(),
  ],
  tracesSampleRate: 1.0, // Sample rate for performance
  environment: process.env.NODE_ENV,
  release: process.env.REACT_APP_VERSION,
  
  // Upload source maps for readable stack traces
  beforeSend(event, hint) {
    // Filter sensitive data
    if (event.request) {
      delete event.request.cookies;
    }
    return event;
  }
});

// Wrap app with Sentry ErrorBoundary
function App() {
  return (
    <Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
      <YourApp />
    </Sentry.ErrorBoundary>
  );
}

// Add user context
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username
});

// Add breadcrumbs for debugging
Sentry.addBreadcrumb({
  category: 'navigation',
  message: 'User navigated to dashboard',
  level: 'info'
});

// Manual error capture
try {
  riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      section: 'payment',
      severity: 'critical'
    },
    contexts: {
      transaction: {
        id: transactionId,
        amount: amount
      }
    }
  });
}

Example: LogRocket integration

// Install
npm install logrocket logrocket-react

// Initialize
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

LogRocket.init('YOUR_APP_ID');
setupLogRocketReact(LogRocket);

// Identify users
LogRocket.identify(user.id, {
  name: user.name,
  email: user.email,
  subscriptionType: user.plan
});

// Integration with Sentry
import * as Sentry from '@sentry/react';

LogRocket.getSessionURL(sessionURL => {
  Sentry.configureScope(scope => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// Track custom events
LogRocket.track('Purchase Completed', {
  productId: product.id,
  revenue: price,
  category: product.category
});

// Sanitize sensitive data
LogRocket.init('YOUR_APP_ID', {
  dom: {
    inputSanitizer: true, // Sanitize input values
  },
  network: {
    requestSanitizer: request => {
      // Remove sensitive headers
      if (request.headers['Authorization']) {
        request.headers['Authorization'] = 'REDACTED';
      }
      return request;
    },
    responseSanitizer: response => {
      // Remove sensitive response data
      if (response.body && response.body.creditCard) {
        response.body.creditCard = 'REDACTED';
      }
      return response;
    }
  }
});

Example: Production error boundary with reporting

import { Component } from 'react';
import * as Sentry from '@sentry/react';

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

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

  componentDidCatch(error, errorInfo) {
    // Log to console in development
    if (process.env.NODE_ENV === 'development') {
      console.error('Error caught by boundary:', error, errorInfo);
    }
    
    // Report to error tracking service
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack
        }
      }
    });
    
    // Log to analytics
    analytics.track('error_boundary_triggered', {
      error: error.message,
      component: errorInfo.componentStack.split('\n')[1]
    });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div role="alert">
          <h2>Something went wrong</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
          </details>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Example: Source map configuration for production debugging

// Create React App - .env.production
GENERATE_SOURCEMAP=true

// Upload source maps to Sentry
// package.json
{
  "scripts": {
    "build": "react-scripts build",
    "sentry:upload": "sentry-cli releases files $npm_package_version upload-sourcemaps ./build"
  }
}

// Webpack config (for custom setup)
module.exports = {
  devtool: 'source-map', // Generate source maps
  plugins: [
    new SentryWebpackPlugin({
      include: './build',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties',
      release: process.env.REACT_APP_VERSION
    })
  ]
};

// .sentryignore
node_modules/
*.map

// Only upload source maps, don't serve them publicly
// Serve source maps only to authenticated users or keep them private
// Configure error tracking to fetch maps from your servers
Production Debugging Best Practices: Always use error boundaries, upload source maps to error tracking service, sanitize sensitive data before logging, add user context to errors, implement breadcrumbs for error context, monitor error rates and trends, set up alerts for critical errors, test error tracking in staging, use session replay for complex bugs, correlate errors with releases.

React Developer Tools and Debugging Summary

19. React Ecosystem Integration

19.1 React Router and Navigation Patterns

Feature API Example Use Case
Basic Routing BrowserRouter, Routes, Route <Route path="/about" element={<About />} /> Define app routes and components
Dynamic Routes :param syntax, useParams() <Route path="/user/:id" /> Routes with variable segments
Navigation Link, NavLink, useNavigate() <Link to="/about">About</Link> Navigate between routes
Nested Routes Outlet, nested Route <Route path="dashboard" element={<Layout />}> Layout with child routes
Protected Routes Navigate, conditional rendering {!auth ? <Navigate to="/login" /> : children} Restrict access by auth status
Query Params useSearchParams() const [params, setParams] = useSearchParams() Read/write URL query strings
Lazy Loading React.lazy(), Suspense const About = lazy(() => import('./About')) Code-split route components

Example: React Router v6 setup

// Install
npm install react-router-dom

// App.tsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users">Users</Link>
      </nav>
      
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />} />
        <Route path="/users/:id" element={<UserProfile />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// Dynamic route with useParams
function UserProfile() {
  const { id } = useParams();
  return <div>User Profile: {id}</div>;
}

// Programmatic navigation
function LoginButton() {
  const navigate = useNavigate();
  
  const handleLogin = async () => {
    await login();
    navigate('/dashboard', { replace: true });
  };
  
  return <button onClick={handleLogin}>Login</button>;
}

Example: Nested routes and layouts

import { Outlet } from 'react-router-dom';

// Layout component with Outlet
function DashboardLayout() {
  return (
    <div>
      <header>Dashboard Header</header>
      <aside>Sidebar</aside>
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
    </div>
  );
}

// App with nested routes
function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route index element={<DashboardHome />} />
        <Route path="profile" element={<Profile />} />
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>
  );
}

// Protected route wrapper
function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth();
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  
  return children;
}

// Usage
<Route 
  path="/dashboard" 
  element={
    <ProtectedRoute>
      <DashboardLayout />
    </ProtectedRoute>
  }
/>

19.2 State Management Libraries (Redux, Zustand)

Library Key Features Core API Best For
Redux Toolkit Reducers, actions, middleware, DevTools configureStore, createSlice, createAsyncThunk Large apps, complex state, predictable updates
Zustand Minimal, no boilerplate, React-first create(), set(), get() Simple global state, quick setup, small apps
Jotai Atomic state, bottom-up approach atom(), useAtom() Fine-grained reactivity, derived state
Recoil Atoms, selectors, async state atom(), selector(), useRecoilState() Complex derived state, async dependencies
MobX Observable state, automatic tracking makeObservable, observer, action OOP patterns, automatic reactivity
Valtio Proxy-based, mutable syntax proxy(), useSnapshot() Mutable-style state, simple API

Example: Redux Toolkit setup

// Install
npm install @reduxjs/toolkit react-redux

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CounterState {
  value: number;
}

const initialState: CounterState = { value: 0 };

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1; // Immer makes this safe
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

// App.tsx
import { Provider } from 'react-redux';
import { store } from './store';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

// Counter.tsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
      <button onClick={() => dispatch(decrement())}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  );
}

Example: Zustand - minimal state management

// Install
npm install zustand

// store.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

interface BearStore {
  bears: number;
  increase: () => void;
  decrease: () => void;
  reset: () => void;
}

export const useBearStore = create<BearStore>()(
  devtools(
    persist(
      (set) => ({
        bears: 0,
        increase: () => set((state) => ({ bears: state.bears + 1 })),
        decrease: () => set((state) => ({ bears: state.bears - 1 })),
        reset: () => set({ bears: 0 }),
      }),
      { name: 'bear-storage' }
    )
  )
);

// Component.tsx
function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears} bears</h1>;
}

function Controls() {
  const increase = useBearStore((state) => state.increase);
  const decrease = useBearStore((state) => state.decrease);
  
  return (
    <div>
      <button onClick={decrease}>-</button>
      <button onClick={increase}>+</button>
    </div>
  );
}

// Async actions
interface UserStore {
  user: User | null;
  fetchUser: (id: string) => Promise<void>;
}

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  fetchUser: async (id) => {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    set({ user });
  },
}));

19.3 UI Component Libraries Integration

Library Design System Key Features Bundle Size
Material-UI (MUI) Material Design Comprehensive components, theming, customization, TypeScript Large (~300KB min+gzip)
Ant Design Enterprise UI 200+ components, i18n, form validation, charts Large (~500KB min+gzip)
Chakra UI Accessible, composable Style props, dark mode, responsive, a11y-first Medium (~150KB min+gzip)
Mantine Modern, feature-rich 120+ components, hooks, form management, notifications Medium (~180KB min+gzip)
shadcn/ui Copy-paste components Radix UI + Tailwind, full control, no package dependency Small (only what you use)
Radix UI Headless components Unstyled, accessible primitives, full control Small (~50KB per component)
Headless UI Tailwind Labs Unstyled, accessible, works with Tailwind Small (~20KB min+gzip)

Example: Material-UI (MUI) setup

// Install
npm install @mui/material @emotion/react @emotion/styled

// App.tsx
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Button, Container, Typography, Box } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline';

const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
    },
    secondary: {
      main: '#dc004e',
    },
  },
});

function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Container>
        <Typography variant="h1" component="h1" gutterBottom>
          Hello MUI
        </Typography>
        <Box sx={{ display: 'flex', gap: 2 }}>
          <Button variant="contained" color="primary">
            Primary
          </Button>
          <Button variant="outlined" color="secondary">
            Secondary
          </Button>
        </Box>
      </Container>
    </ThemeProvider>
  );
}

Example: Chakra UI setup

// Install
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion

// App.tsx
import { ChakraProvider, Box, Button, Heading, VStack } from '@chakra-ui/react';
import { extendTheme } from '@chakra-ui/react';

const theme = extendTheme({
  colors: {
    brand: {
      50: '#e3f2fd',
      500: '#2196f3',
      900: '#0d47a1',
    },
  },
});

function App() {
  return (
    <ChakraProvider theme={theme}>
      <Box p={8}>
        <VStack spacing={4} align="stretch">
          <Heading size="xl">Hello Chakra</Heading>
          <Button colorScheme="brand" size="lg">
            Click Me
          </Button>
          <Box bg="gray.100" p={4} borderRadius="md">
            Responsive Box
          </Box>
        </VStack>
      </Box>
    </ChakraProvider>
  );
}

Example: shadcn/ui - copy-paste approach

// Install CLI
npx shadcn-ui@latest init

// Add components
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card

// Components are copied to your project
// components/ui/button.tsx
// components/ui/card.tsx

// Usage
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';

function App() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Welcome</CardTitle>
      </CardHeader>
      <CardContent>
        <Button variant="default">Click Me</Button>
        <Button variant="outline">Outline</Button>
      </CardContent>
    </Card>
  );
}

// Full control - edit copied components directly
// Tailwind-based styling
// No package lock-in

19.4 CSS-in-JS Solutions and Styling

Solution Approach Features Performance
styled-components Tagged templates Automatic vendor prefixing, theming, SSR, TypeScript Runtime CSS generation
Emotion Tagged templates or object Fast, flexible, source maps, framework agnostic Runtime, faster than styled-components
Tailwind CSS Utility classes JIT compiler, purging, plugins, mobile-first Zero runtime, build-time
CSS Modules Scoped CSS files Local scope, composition, type-safe with TypeScript Zero runtime, standard CSS
Vanilla Extract Zero-runtime CSS-in-TS Type-safe, theme contracts, build-time extraction Zero runtime, static CSS
Linaria Zero-runtime CSS-in-JS Build-time extraction, critical CSS, SSR Zero runtime, extracted CSS
Panda CSS Build-time atomic CSS Type-safe, recipes, variants, zero runtime Zero runtime, optimized output

Example: styled-components

// Install
npm install styled-components

// Component.tsx
import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.primary ? '#007bff' : '#6c757d'};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    opacity: 0.8;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

const Container = styled.div`
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  
  @media (max-width: 768px) {
    padding: 10px;
  }
`;

// Usage
function App() {
  return (
    <Container>
      <Button primary>Primary</Button>
      <Button>Secondary</Button>
    </Container>
  );
}

// Theming
import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
  },
  spacing: {
    small: '8px',
    medium: '16px',
  },
};

const ThemedButton = styled.button`
  background: ${props => props.theme.colors.primary};
  padding: ${props => props.theme.spacing.medium};
`;

function App() {
  return (
    <ThemeProvider theme={theme}>
      <ThemedButton>Themed</ThemedButton>
    </ThemeProvider>
  );
}

Example: Tailwind CSS with React

// Install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
        brand: '#007bff',
      },
    },
  },
  plugins: [],
};

// index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

// Component.tsx
function Button({ children, variant = 'primary' }) {
  const baseClasses = 'px-4 py-2 rounded font-medium transition';
  const variantClasses = {
    primary: 'bg-blue-500 text-white hover:bg-blue-600',
    secondary: 'bg-gray-500 text-white hover:bg-gray-600',
    outline: 'border border-blue-500 text-blue-500 hover:bg-blue-50',
  };
  
  return (
    <button className={`${baseClasses} ${variantClasses[variant]}`}>
      {children}
    </button>
  );
}

// With clsx for conditional classes
import clsx from 'clsx';

function Card({ children, active }) {
  return (
    <div className={clsx(
      'p-4 rounded-lg shadow',
      active ? 'bg-blue-100 border-blue-500' : 'bg-white border-gray-200',
      'border-2 transition-colors'
    )}>
      {children}
    </div>
  );
}

Example: CSS Modules

// Button.module.css
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: opacity 0.2s;
}

.button:hover {
  opacity: 0.8;
}

.primary {
  background: #007bff;
  color: white;
}

.secondary {
  background: #6c757d;
  color: white;
}

.button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

// Button.tsx
import styles from './Button.module.css';
import clsx from 'clsx';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  children: React.ReactNode;
}

function Button({ variant = 'primary', disabled, children }: ButtonProps) {
  return (
    <button 
      className={clsx(styles.button, styles[variant])}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

// Composition
.base {
  padding: 10px;
}

.large {
  composes: base;
  padding: 20px;
  font-size: 18px;
}

19.5 Form Libraries and Validation Solutions

Library Key Features Validation Best For
React Hook Form Performance-focused, minimal re-renders, small bundle Built-in, Yup, Zod, Joi integration Complex forms, performance-critical apps
Formik Popular, full-featured, field-level validation Built-in, Yup integration Standard forms, familiar API
Final Form Framework-agnostic, subscription-based updates Custom validators, async validation Flexible form state management
Zod TypeScript-first schema validation Type inference, parse, transform TypeScript projects, type-safe forms
Yup Schema-based validation, chainable API Sync/async validation, custom rules Form validation, data validation
TanStack Form Framework-agnostic, type-safe, adapters Custom validators, async, field-level Modern forms, framework flexibility

Example: React Hook Form with Zod

// Install
npm install react-hook-form @hookform/resolvers zod

// LoginForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// Define schema
const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  rememberMe: z.boolean().optional(),
});

type LoginFormData = z.infer<typeof loginSchema>;

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginFormData) => {
    await loginUser(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="email">Email</label>
        <input 
          {...register('email')} 
          type="email" 
          id="email"
        />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input 
          {...register('password')} 
          type="password" 
          id="password"
        />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <div>
        <label>
          <input {...register('rememberMe')} type="checkbox" />
          Remember Me
        </label>
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

Example: Dynamic fields and arrays

import { useForm, useFieldArray } from 'react-hook-form';

const schema = z.object({
  name: z.string().min(1),
  emails: z.array(
    z.object({
      value: z.string().email(),
      primary: z.boolean(),
    })
  ).min(1, 'At least one email required'),
});

function ProfileForm() {
  const { register, control, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
    defaultValues: {
      name: '',
      emails: [{ value: '', primary: true }],
    },
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'emails',
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="Name" />
      {errors.name && <span>{errors.name.message}</span>}

      {fields.map((field, index) => (
        <div key={field.id}>
          <input
            {...register(`emails.${index}.value`)}
            placeholder="Email"
          />
          <label>
            <input
              {...register(`emails.${index}.primary`)}
              type="checkbox"
            />
            Primary
          </label>
          <button type="button" onClick={() => remove(index)}>
            Remove
          </button>
        </div>
      ))}

      <button type="button" onClick={() => append({ value: '', primary: false })}>
        Add Email
      </button>

      <button type="submit">Submit</button>
    </form>
  );
}

19.6 Data Fetching Libraries (SWR, React Query)

Library Features Key Benefits Use Case
TanStack Query (React Query) Caching, refetching, pagination, infinite scroll, mutations Automatic background updates, optimistic updates, devtools Complex data fetching, real-time updates
SWR Stale-while-revalidate, focus revalidation, lightweight Simple API, automatic revalidation, fast Simple data fetching, real-time apps
Apollo Client GraphQL-focused, caching, subscriptions, local state GraphQL integration, normalized cache, tooling GraphQL APIs, complex data graphs
RTK Query Redux Toolkit integration, auto-generated hooks Redux integration, code generation, TypeScript Redux apps, REST/GraphQL APIs
tRPC End-to-end type safety, React Query integration No code generation, type inference, full-stack TypeScript full-stack apps

Example: React Query (TanStack Query)

// Install
npm install @tanstack/react-query

// Setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1 minute
      refetchOnWindowFocus: false,
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

// Basic query
import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>{data.name}</div>;
}

// Mutations
import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreatePost() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });

  return (
    <button onClick={() => mutation.mutate({ title: 'New Post' })}>
      Create Post
    </button>
  );
}

// Optimistic updates
const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    const previousTodos = queryClient.getQueryData(['todos']);
    
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
    
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

Example: SWR - simple data fetching

// Install
npm install swr

// Basic usage
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher);

  if (error) return <div>Failed to load</div>;
  if (isLoading) return <div>Loading...</div>;

  return <div>Hello {data.name}!</div>;
}

// Global config
import { SWRConfig } from 'swr';

function App() {
  return (
    <SWRConfig 
      value={{
        fetcher: (url) => fetch(url).then(res => res.json()),
        refreshInterval: 3000,
        revalidateOnFocus: true,
      }}
    >
      <Dashboard />
    </SWRConfig>
  );
}

// Mutations
import useSWRMutation from 'swr/mutation';

async function updateUser(url, { arg }) {
  await fetch(url, {
    method: 'PUT',
    body: JSON.stringify(arg)
  });
}

function Settings() {
  const { trigger, isMutating } = useSWRMutation('/api/user', updateUser);

  return (
    <button 
      disabled={isMutating}
      onClick={() => trigger({ name: 'New Name' })}
    >
      Update User
    </button>
  );
}

// Pagination
function Projects() {
  const [pageIndex, setPageIndex] = useState(0);
  
  const { data } = useSWR(`/api/projects?page=${pageIndex}`, fetcher);

  return (
    <div>
      {data?.projects.map(p => <div key={p.id}>{p.name}</div>)}
      <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
      <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
    </div>
  );
}

Example: Infinite scroll with React Query

import { useInfiniteQuery } from '@tanstack/react-query';
import { useInView } from 'react-intersection-observer';

function Posts() {
  const { ref, inView } = useInView();

  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  });

  useEffect(() => {
    if (inView && hasNextPage) {
      fetchNextPage();
    }
  }, [inView, hasNextPage, fetchNextPage]);

  return (
    <div>
      {data?.pages.map((page) => (
        <div key={page.nextCursor}>
          {page.posts.map((post) => (
            <div key={post.id}>{post.title}</div>
          ))}
        </div>
      ))}
      
      <div ref={ref}>
        {isFetchingNextPage ? 'Loading more...' : hasNextPage ? 'Load More' : 'Nothing more'}
      </div>
    </div>
  );
}
Ecosystem Integration Tips: Choose libraries based on project needs, bundle size matters, prefer TypeScript-friendly libraries, check maintenance and community, test library compatibility, use tree-shaking when available, consider migration path, read documentation thoroughly, start simple then add complexity, benchmark performance in your app.

React Ecosystem Integration Summary

20. React Security and Best Practices

20.1 XSS Prevention and Input Sanitization

Attack Vector Risk Prevention Example
User Input in JSX XSS via script injection React automatically escapes JSX expressions {userInput} is safe - React escapes HTML
URL Parameters JavaScript execution via href Validate and sanitize URLs, block javascript: protocol Use allowlist for protocols (http:, https:, mailto:)
HTML Attributes Event handler injection Never use user input directly in event handlers Avoid: onClick={eval(userInput)}
innerHTML/outerHTML Direct HTML injection Use React rendering, avoid dangerouslySetInnerHTML Sanitize with DOMPurify if HTML needed
CSS Injection Style-based XSS Validate style values, use CSS-in-JS safely Avoid: style={{'{{'}}background: userInput}}
SVG Content Script tags in SVG Sanitize SVG content, validate sources Use SVG as components, not raw strings

Example: Safe rendering of user input

// ✅ SAFE - React automatically escapes
function UserComment({ comment }) {
  return <div>{comment.text}</div>; // HTML entities escaped
}

// ✅ SAFE - Using textContent
function DisplayText({ text }) {
  const ref = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (ref.current) {
      ref.current.textContent = text; // Safe, no HTML parsing
    }
  }, [text]);
  
  return <div ref={ref} />;
}

// ❌ UNSAFE - Direct href from user input
function UnsafeLink({ url }) {
  return <a href={url}>Click</a>; // Can be javascript:alert('XSS')
}

// ✅ SAFE - Validate URL protocol
function SafeLink({ url }) {
  const isValidUrl = (url: string) => {
    try {
      const parsed = new URL(url);
      return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
    } catch {
      return false;
    }
  };
  
  if (!isValidUrl(url)) {
    return <span>Invalid URL</span>;
  }
  
  return <a href={url} rel="noopener noreferrer">Click</a>;
}

Example: Input sanitization library

// Install DOMPurify for HTML sanitization
npm install dompurify
npm install --save-dev @types/dompurify

import DOMPurify from 'dompurify';

// Sanitize HTML content
function SanitizedHtml({ html }: { html: string }) {
  const sanitized = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
    ALLOWED_ATTR: ['href', 'title', 'target'],
    ALLOW_DATA_ATTR: false,
  });
  
  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}

// Sanitize user input before storing
function CommentForm() {
  const [comment, setComment] = useState('');
  
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    
    // Sanitize before sending to API
    const sanitized = DOMPurify.sanitize(comment, {
      ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
      ALLOWED_ATTR: [],
    });
    
    await saveComment(sanitized);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <textarea 
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        maxLength={500}
      />
      <button type="submit">Post Comment</button>
    </form>
  );
}

20.2 dangerouslySetInnerHTML Safe Usage

Risk Mitigation Best Practice Example
XSS Attacks Always sanitize HTML before rendering Use DOMPurify or similar library Sanitize all user-generated content
Script Injection Remove script tags and event handlers Configure strict sanitization rules Block <script>, onclick, onerror, etc.
Style Attacks Limit allowed CSS properties Whitelist safe CSS properties only Avoid expression(), url(), import
Data Exfiltration Block external resource loading CSP headers, sanitize img/link src Restrict to same-origin or trusted domains
iframe Injection Block iframe tags unless necessary Use sandbox attribute if needed Restrict iframe capabilities
Form Hijacking Block form tags in user content Sanitize or block form elements Prevent form submission hijacking

Example: Safe dangerouslySetInnerHTML usage

import DOMPurify from 'dompurify';

// ❌ NEVER DO THIS - Unsafe!
function UnsafeHtml({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

// ✅ SAFE - Sanitized HTML
function SafeHtml({ html }: { html: string }) {
  const sanitized = useMemo(() => {
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS: [
        'h1', 'h2', 'h3', 'p', 'br', 'strong', 'em', 'u',
        'ul', 'ol', 'li', 'a', 'blockquote', 'code', 'pre'
      ],
      ALLOWED_ATTR: {
        'a': ['href', 'title', 'target', 'rel'],
        'img': ['src', 'alt', 'title', 'width', 'height']
      },
      ALLOW_DATA_ATTR: false,
      FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form'],
      FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover']
    });
  }, [html]);
  
  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}

// ✅ BETTER - Use React components instead
function MarkdownRenderer({ markdown }: { markdown: string }) {
  // Use a library like react-markdown
  return <ReactMarkdown>{markdown}</ReactMarkdown>;
}

// Safe HTML with hooks
function useSafeHtml(html: string) {
  return useMemo(() => {
    if (!html) return '';
    
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
      ALLOWED_ATTR: ['href'],
      KEEP_CONTENT: true,
    });
  }, [html]);
}

function RichTextDisplay({ content }: { content: string }) {
  const safeHtml = useSafeHtml(content);
  
  if (!content) {
    return <p>No content available</p>;
  }
  
  return (
    <div 
      className="rich-text-content"
      dangerouslySetInnerHTML={{ __html: safeHtml }}
    />
  );
}
Warning: Never use dangerouslySetInnerHTML with unsanitized user input. Always validate HTML source, sanitize with DOMPurify, use strict whitelist of tags/attributes, implement Content Security Policy, prefer React components over raw HTML, audit third-party HTML carefully.

20.3 Props Validation and Runtime Type Checking

Approach Tool Features Use Case
PropTypes prop-types package Runtime validation, custom validators, development warnings JavaScript projects, simple validation
TypeScript Built-in type system Compile-time checking, inference, strict mode Type-safe React apps, preferred approach
Zod Runtime zod schemas Runtime + compile-time, parsing, transformation API responses, form validation, external data
io-ts TypeScript runtime validation Type guards, decoders, reporters Complex validation, FP patterns
Yup Schema validation Object schemas, async validation, transforms Form validation, data validation
Custom Validation Manual checks Full control, specific business logic Complex validation requirements

Example: PropTypes validation

import PropTypes from 'prop-types';

function UserCard({ user, onEdit, isActive }) {
  return (
    <div className={isActive ? 'active' : ''}>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={onEdit}>Edit</button>
    </div>
  );
}

UserCard.propTypes = {
  user: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
    avatar: PropTypes.string,
  }).isRequired,
  onEdit: PropTypes.func.isRequired,
  isActive: PropTypes.bool,
};

UserCard.defaultProps = {
  isActive: false,
};

// Custom validators
function EmailValidator(props, propName, componentName) {
  const email = props[propName];
  
  if (email && !/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email)) {
    return new Error(
      `Invalid prop \`${propName}\` supplied to \`${componentName}\`. ` +
      `Expected a valid email address.`
    );
  }
}

function ContactForm({ email }) {
  return <input type="email" value={email} />;
}

ContactForm.propTypes = {
  email: EmailValidator
};

Example: TypeScript with runtime validation

import { z } from 'zod';

// Define schema
const UserSchema = z.object({
  id: z.number().positive(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().min(0).max(150).optional(),
  role: z.enum(['admin', 'user', 'guest']),
});

type User = z.infer<typeof UserSchema>;

// Component with TypeScript
interface UserCardProps {
  user: User;
  onEdit: (user: User) => void;
  isActive?: boolean;
}

function UserCard({ user, onEdit, isActive = false }: UserCardProps) {
  return (
    <div className={isActive ? 'active' : ''}>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={() => onEdit(user)}>Edit</button>
    </div>
  );
}

// Validate API response
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();
  
  // Runtime validation
  try {
    return UserSchema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error('Invalid user data:', error.errors);
      throw new Error('Invalid user data from API');
    }
    throw error;
  }
}

// Safe parse without throwing
async function fetchUserSafe(id: number) {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();
  
  const result = UserSchema.safeParse(data);
  
  if (!result.success) {
    return { error: result.error, data: null };
  }
  
  return { error: null, data: result.data };
}

20.4 Authentication and Authorization Patterns

Pattern Implementation Security Considerations Use Case
JWT Tokens Store in memory or httpOnly cookies Never store in localStorage, use short expiry, refresh tokens Stateless authentication, API access
Session Cookies Server-side sessions, httpOnly secure cookies CSRF protection, SameSite attribute, secure flag Traditional web apps, server-side auth
OAuth 2.0 / OIDC Third-party auth providers (Google, Auth0) PKCE flow for SPAs, validate state parameter Social login, enterprise SSO
Protected Routes Conditional rendering, redirect logic Server-side validation, check permissions Role-based access control
API Security Authorization headers, CORS configuration Validate on server, don't trust client API endpoint protection
MFA/2FA Time-based OTP, SMS, authenticator apps Backup codes, rate limiting, secure storage Enhanced security for sensitive ops

Example: JWT authentication with refresh tokens

// AuthContext.tsx
interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [accessToken, setAccessToken] = useState<string | null>(null);

  // Store access token in memory only
  const login = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include', // Include httpOnly refresh token cookie
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) throw new Error('Login failed');

    const { user, accessToken } = await response.json();
    setUser(user);
    setAccessToken(accessToken); // Store in memory
  };

  const logout = async () => {
    await fetch('/api/auth/logout', {
      method: 'POST',
      credentials: 'include',
    });
    setUser(null);
    setAccessToken(null);
  };

  // Refresh access token
  useEffect(() => {
    const refreshToken = async () => {
      try {
        const response = await fetch('/api/auth/refresh', {
          method: 'POST',
          credentials: 'include', // Send httpOnly cookie
        });

        if (response.ok) {
          const { user, accessToken } = await response.json();
          setUser(user);
          setAccessToken(accessToken);
        }
      } catch (error) {
        console.error('Token refresh failed:', error);
      }
    };

    refreshToken();

    // Refresh before expiry (every 14 minutes for 15-minute tokens)
    const interval = setInterval(refreshToken, 14 * 60 * 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <AuthContext.Provider value={{ user, login, logout, isAuthenticated: !!user }}>
      {children}
    </AuthContext.Provider>
  );
}

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

Example: Protected routes and role-based access

// ProtectedRoute.tsx
interface ProtectedRouteProps {
  children: ReactNode;
  requiredRole?: string[];
  redirectTo?: string;
}

function ProtectedRoute({ 
  children, 
  requiredRole, 
  redirectTo = '/login' 
}: ProtectedRouteProps) {
  const { user, isAuthenticated } = useAuth();
  const location = useLocation();

  if (!isAuthenticated) {
    // Redirect to login, preserve intended destination
    return <Navigate to={redirectTo} state={{ from: location }} replace />;
  }

  // Check role-based access
  if (requiredRole && !requiredRole.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return <>{children}</>;
}

// Usage in routes
function App() {
  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      <Route path="/unauthorized" element={<Unauthorized />} />
      
      <Route
        path="/dashboard"
        element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        }
      />
      
      <Route
        path="/admin"
        element={
          <ProtectedRoute requiredRole={['admin']}>
            <AdminPanel />
          </ProtectedRoute>
        }
      />
    </Routes>
  );
}

// Secure API requests
function useSecureApi() {
  const { accessToken } = useAuth();

  const secureRequest = async (url: string, options: RequestInit = {}) => {
    const response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      credentials: 'include',
    });

    if (response.status === 401) {
      // Token expired, trigger logout
      throw new Error('Unauthorized');
    }

    return response;
  };

  return { secureRequest };
}

20.5 Secure State Management Practices

Risk Secure Practice Implementation Example
Sensitive Data in State Never store passwords, credit cards, SSN in state Use secure backend storage, tokenization Store tokens server-side, use references only
localStorage Risks Avoid storing auth tokens in localStorage Use httpOnly cookies or memory storage XSS can access localStorage but not httpOnly cookies
State Persistence Encrypt sensitive persisted state Use encryption libraries, secure key management Encrypt before saving to storage
Redux DevTools Disable in production or sanitize actions Conditionally enable, filter sensitive data Redact passwords, tokens from action logs
State Leakage Clear sensitive state on logout Reset stores, clear memory Unmount components, clear caches
Cross-Tab State Validate state across browser tabs Use BroadcastChannel for sync, validate auth Logout all tabs when one logs out

Example: Secure state management

// ❌ NEVER DO THIS - Insecure!
localStorage.setItem('authToken', token);
localStorage.setItem('password', password);
localStorage.setItem('creditCard', cardNumber);

// ✅ SECURE - In-memory only, httpOnly cookies for refresh
const AuthProvider = ({ children }) => {
  // Access token in memory only
  const [accessToken, setAccessToken] = useState<string | null>(null);
  
  // Refresh token in httpOnly cookie (set by server)
  // Cookie: refreshToken=xxx; HttpOnly; Secure; SameSite=Strict
  
  return (
    <AuthContext.Provider value={{ accessToken }}>
      {children}
    </AuthContext.Provider>
  );
};

// Secure state persistence
import CryptoJS from 'crypto-js';

function useSecureStorage(key: string) {
  const encryptionKey = process.env.REACT_APP_ENCRYPTION_KEY;
  
  const setSecureItem = (value: any) => {
    const encrypted = CryptoJS.AES.encrypt(
      JSON.stringify(value),
      encryptionKey
    ).toString();
    
    localStorage.setItem(key, encrypted);
  };
  
  const getSecureItem = () => {
    const encrypted = localStorage.getItem(key);
    if (!encrypted) return null;
    
    try {
      const decrypted = CryptoJS.AES.decrypt(encrypted, encryptionKey);
      return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));
    } catch {
      return null;
    }
  };
  
  const removeSecureItem = () => {
    localStorage.removeItem(key);
  };
  
  return { setSecureItem, getSecureItem, removeSecureItem };
}

// Redux DevTools - sanitize sensitive data
const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production' && {
    actionSanitizer: (action) => ({
      ...action,
      payload: action.type.includes('PASSWORD') 
        ? '***REDACTED***' 
        : action.payload,
    }),
    stateSanitizer: (state) => ({
      ...state,
      auth: {
        ...state.auth,
        password: '***REDACTED***',
        token: '***REDACTED***',
      },
    }),
  },
});

// Clear state on logout
function useSecureLogout() {
  const dispatch = useDispatch();
  const queryClient = useQueryClient();
  
  const logout = useCallback(async () => {
    // Clear Redux state
    dispatch({ type: 'RESET_STATE' });
    
    // Clear React Query cache
    queryClient.clear();
    
    // Clear any local storage (except user preferences)
    const preferences = localStorage.getItem('userPreferences');
    localStorage.clear();
    if (preferences) {
      localStorage.setItem('userPreferences', preferences);
    }
    
    // Call logout API
    await fetch('/api/auth/logout', {
      method: 'POST',
      credentials: 'include',
    });
    
    // Redirect to login
    window.location.href = '/login';
  }, [dispatch, queryClient]);
  
  return logout;
}

20.6 Content Security Policy with React Apps

CSP Directive Purpose React Configuration Example Value
default-src Fallback for other directives Set restrictive default, override as needed 'self'
script-src Control script execution Use nonce or hash for inline scripts 'self' 'nonce-xyz123'
style-src Control stylesheet loading Use nonce for CSS-in-JS libraries 'self' 'unsafe-inline' (use nonce instead)
img-src Control image sources Whitelist CDNs and data URIs if needed 'self' data: https://cdn.example.com
connect-src Control fetch/XHR destinations Whitelist API endpoints 'self' https://api.example.com
frame-ancestors Prevent clickjacking Control iframe embedding 'none' or 'self'
upgrade-insecure-requests Auto upgrade HTTP to HTTPS Enable for production No value needed (directive only)

Example: CSP header configuration

// Next.js - next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self' 'unsafe-eval' 'unsafe-inline'", // Adjust for production
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self' data:",
      "connect-src 'self' https://api.example.com",
      "frame-ancestors 'none'",
      "base-uri 'self'",
      "form-action 'self'",
      "upgrade-insecure-requests",
    ].join('; '),
  },
  {
    key: 'X-Frame-Options',
    value: 'DENY',
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin',
  },
  {
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=()',
  },
];

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ];
  },
};

// Express.js backend
import helmet from 'helmet';

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"], // Use nonce in production
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.example.com"],
      fontSrc: ["'self'", "data:"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
      frameAncestors: ["'none'"],
      baseUri: ["'self'"],
      formAction: ["'self'"],
      upgradeInsecureRequests: [],
    },
  })
);

Example: Using CSP nonces with React

// Server-side (Express with React SSR)
import crypto from 'crypto';
import { renderToString } from 'react-dom/server';

app.get('*', (req, res) => {
  // Generate nonce for this request
  const nonce = crypto.randomBytes(16).toString('base64');
  
  // Set CSP header with nonce
  res.setHeader(
    'Content-Security-Policy',
    `script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'`
  );
  
  // Render app with nonce
  const html = renderToString(<App nonce={nonce} />);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <style nonce="${nonce}">
          /* Critical CSS */
        </style>
      </head>
      <body>
        <div id="root">${html}</div>
        <script nonce="${nonce}" src="/static/bundle.js"></script>
      </body>
    </html>
  `);
});

// React component using nonce
function App({ nonce }) {
  return (
    <HelmetProvider>
      <Helmet>
        <script nonce={nonce}>
          {`window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};`}
        </script>
      </Helmet>
      <YourApp />
    </HelmetProvider>
  );
}

// Strict CSP for production
const productionCSP = {
  defaultSrc: ["'none'"],
  scriptSrc: ["'self'"],
  styleSrc: ["'self'"],
  imgSrc: ["'self'", "data:", "https://trusted-cdn.com"],
  fontSrc: ["'self'"],
  connectSrc: ["'self'", "https://api.example.com"],
  frameSrc: ["'none'"],
  objectSrc: ["'none'"],
  baseUri: ["'self'"],
  formAction: ["'self'"],
  frameAncestors: ["'none'"],
  upgradeInsecureRequests: [],
};
CSP Best Practices: Start with strict policy, test thoroughly before production, avoid 'unsafe-inline' and 'unsafe-eval', use nonces or hashes for inline scripts, whitelist specific domains not wildcards, implement CSP reporting endpoint, monitor violations, update policy as app evolves, test in report-only mode first.

React Security Best Practices Summary

21. React TypeScript Integration

21.1 Component Props TypeScript Interfaces

Pattern Syntax Use Case Example
Basic Interface interface Props { ... } Define component prop types Simple props with required/optional fields
Type Alias type Props = { ... } Alternative to interface, unions, intersections When needing union types or primitives
Optional Props prop?: string Props that may not be provided Default values, conditional rendering
Default Props Destructure with defaults Provide fallback values { size = 'medium' }
Children Prop children: React.ReactNode Type children elements Wrapper components, layouts
Generic Props interface Props<T> { ... } Type-safe generic components Lists, tables, data displays
Discriminated Unions type Props = A | B Variant props based on type field Button variants, conditional props

Example: Component props interfaces

// Basic interface
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: 'primary' | 'secondary' | 'danger';
}

function Button({ label, onClick, disabled = false, variant = 'primary' }: ButtonProps) {
  return (
    <button 
      onClick={onClick} 
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  );
}

// Children prop
interface CardProps {
  title: string;
  children: React.ReactNode;
  footer?: React.ReactNode;
}

function Card({ title, children, footer }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// Generic props
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// Usage with type inference
<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
  keyExtractor={(user) => user.id}
/>

Example: Discriminated unions for variant props

// Button with discriminated union
type BaseButtonProps = {
  label: string;
  disabled?: boolean;
};

type PrimaryButtonProps = BaseButtonProps & {
  variant: 'primary';
  color: 'blue' | 'green' | 'red';
};

type SecondaryButtonProps = BaseButtonProps & {
  variant: 'secondary';
  outlined: boolean;
};

type LinkButtonProps = BaseButtonProps & {
  variant: 'link';
  href: string;
  target?: '_blank' | '_self';
};

type ButtonProps = PrimaryButtonProps | SecondaryButtonProps | LinkButtonProps;

function Button(props: ButtonProps) {
  const { variant, label, disabled } = props;
  
  if (variant === 'primary') {
    // TypeScript knows props.color exists
    return <button className={`btn-${props.color}`}>{label}</button>;
  }
  
  if (variant === 'secondary') {
    // TypeScript knows props.outlined exists
    return <button className={props.outlined ? 'outlined' : ''}>{label}</button>;
  }
  
  // TypeScript knows props.href exists
  return <a href={props.href} target={props.target}>{label}</a>;
}

// Complex discriminated union
type FormFieldProps =
  | { type: 'text'; maxLength?: number; pattern?: string }
  | { type: 'number'; min?: number; max?: number; step?: number }
  | { type: 'select'; options: Array<{ label: string; value: string }> }
  | { type: 'checkbox'; checked: boolean };

function FormField(props: FormFieldProps & { name: string; label: string }) {
  switch (props.type) {
    case 'text':
      return <input type="text" maxLength={props.maxLength} pattern={props.pattern} />;
    case 'number':
      return <input type="number" min={props.min} max={props.max} step={props.step} />;
    case 'select':
      return (
        <select>
          {props.options.map(opt => <option value={opt.value}>{opt.label}</option>)}
        </select>
      );
    case 'checkbox':
      return <input type="checkbox" checked={props.checked} />;
  }
}

21.2 Hook Type Definitions and Generic Hooks

Hook Type Syntax Type Inference Example
useState useState<Type>(initial) Auto-infers from initial value const [count, setCount] = useState(0)
useRef useRef<HTMLElement | null>(null) Must specify element type const ref = useRef<HTMLDivElement>(null)
useReducer useReducer<Reducer<State, Action>> Type state and action Typed reducer with discriminated unions
useContext useContext<ContextType>(Context) Infers from context creation Type-safe context consumption
useMemo useMemo<ReturnType>(() => ...) Auto-infers return type Type-safe memoization
useCallback useCallback<FunctionType>(...) Infers function signature Type-safe callbacks
Custom Hooks function useHook<T>(): ReturnType Explicit return type Generic reusable hooks

Example: Typed React hooks

// useState with explicit type
const [user, setUser] = useState<User | null>(null);
const [count, setCount] = useState(0); // inferred as number

// useState with complex types
interface FormState {
  email: string;
  password: string;
  errors: Record<string, string>;
}

const [form, setForm] = useState<FormState>({
  email: '',
  password: '',
  errors: {},
});

// useRef with DOM elements
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  if (inputRef.current) {
    inputRef.current.focus(); // TypeScript knows .focus() exists
  }
}, []);

// useRef for mutable values
const intervalRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
  intervalRef.current = setInterval(() => {
    console.log('tick');
  }, 1000);
  
  return () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
  };
}, []);

// useReducer with typed actions
type State = { count: number; lastAction: string };

type Action =
  | { type: 'increment'; payload?: number }
  | { type: 'decrement'; payload?: number }
  | { type: 'reset' };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { 
        count: state.count + (action.payload ?? 1), 
        lastAction: 'increment' 
      };
    case 'decrement':
      return { 
        count: state.count - (action.payload ?? 1), 
        lastAction: 'decrement' 
      };
    case 'reset':
      return { count: 0, lastAction: 'reset' };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, lastAction: 'init' });

// TypeScript validates action types
dispatch({ type: 'increment', payload: 5 }); // ✅
dispatch({ type: 'invalid' }); // ❌ Error

Example: Generic custom hooks

// Generic fetch hook
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      try {
        const response = await fetch(url);
        const json = await response.json();
        
        if (!cancelled) {
          setData(json as T);
          setLoading(false);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err instanceof Error ? err : new Error('Unknown error'));
          setLoading(false);
        }
      }
    }

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [url]);

  return { data, loading, error };
}

// Usage with type inference
interface User {
  id: number;
  name: string;
  email: string;
}

function UserProfile({ userId }: { userId: number }) {
  const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>No user found</div>;
  
  // TypeScript knows user has id, name, email
  return <div>{user.name} - {user.email}</div>;
}

// Generic array hook with constraints
function useArray<T>(initialValue: T[] = []) {
  const [array, setArray] = useState<T[]>(initialValue);

  const push = useCallback((item: T) => {
    setArray(prev => [...prev, item]);
  }, []);

  const remove = useCallback((index: number) => {
    setArray(prev => prev.filter((_, i) => i !== index));
  }, []);

  const update = useCallback((index: number, item: T) => {
    setArray(prev => prev.map((val, i) => i === index ? item : val));
  }, []);

  const clear = useCallback(() => {
    setArray([]);
  }, []);

  return { array, set: setArray, push, remove, update, clear };
}

// Usage
const { array: todos, push, remove } = useArray<Todo>([]);

// Generic storage hook
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);

  return [storedValue, setValue] as const;
}

21.3 Event Handler Type Safety

Event Type React Type Native Type Common Usage
Click React.MouseEvent<HTMLElement> MouseEvent Button clicks, div clicks
Change React.ChangeEvent<HTMLInputElement> Event Input, textarea, select changes
Submit React.FormEvent<HTMLFormElement> Event Form submissions
Focus React.FocusEvent<HTMLElement> FocusEvent Input focus/blur
Keyboard React.KeyboardEvent<HTMLElement> KeyboardEvent Key press, key down, key up
Drag React.DragEvent<HTMLElement> DragEvent Drag and drop interactions

Example: Type-safe event handlers

// Click events
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
  event.preventDefault();
  console.log('Button clicked', event.currentTarget.name);
}

// Different element types
function handleDivClick(event: React.MouseEvent<HTMLDivElement>) {
  console.log('Div clicked at', event.clientX, event.clientY);
}

// Input change events
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
  const value = event.target.value;
  const name = event.target.name;
  console.log(`${name}: ${value}`);
}

// Select change events
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
  const selectedValue = event.target.value;
  const selectedIndex = event.target.selectedIndex;
  console.log(`Selected: ${selectedValue} at index ${selectedIndex}`);
}

// Form submission
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
  event.preventDefault();
  const formData = new FormData(event.currentTarget);
  const data = Object.fromEntries(formData);
  console.log('Form data:', data);
}

// Keyboard events
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault();
    console.log('Enter pressed');
  }
  
  // Check modifiers
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault();
    console.log('Ctrl+S pressed');
  }
}

// Complete form component
interface FormData {
  email: string;
  password: string;
}

function LoginForm() {
  const [formData, setFormData] = useState<FormData>({
    email: '',
    password: '',
  });

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = event.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log('Submitting:', formData);
  };

  return (
    <form onSubmit={handleFormSubmit}>
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleInputChange}
      />
      <input
        type="password"
        name="password"
        value={formData.password}
        onChange={handleInputChange}
      />
      <button type="submit">Login</button>
    </form>
  );
}

Example: Generic event handler types

// Reusable event handler types
type ClickHandler = React.MouseEvent<HTMLButtonElement>;
type InputChangeHandler = React.ChangeEvent<HTMLInputElement>;
type FormSubmitHandler = React.FormEvent<HTMLFormElement>;

interface ButtonProps {
  onClick: (event: ClickHandler) => void;
  label: string;
}

function Button({ onClick, label }: ButtonProps) {
  return <button onClick={onClick}>{label}</button>;
}

// Generic handler wrapper
type ElementClickHandler<T extends HTMLElement> = (
  event: React.MouseEvent<T>
) => void;

interface ClickableProps<T extends HTMLElement> {
  onClick: ElementClickHandler<T>;
  children: React.ReactNode;
}

// Event handler with custom parameters
interface SearchInputProps {
  onSearch: (query: string) => void;
  onClear: () => void;
}

function SearchInput({ onSearch, onClear }: SearchInputProps) {
  const [query, setQuery] = useState('');

  const handleChange: InputChangeHandler = (event) => {
    setQuery(event.target.value);
  };

  const handleSubmit: FormSubmitHandler = (event) => {
    event.preventDefault();
    onSearch(query); // Call with typed parameter
  };

  const handleClear: ClickHandler = (event) => {
    event.preventDefault();
    setQuery('');
    onClear();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={query} onChange={handleChange} />
      <button type="submit">Search</button>
      <button type="button" onClick={handleClear}>Clear</button>
    </form>
  );
}

21.4 Context API TypeScript Patterns

Pattern Implementation Benefits Use Case
Typed Context createContext<Type | null>(null) Type safety for context value All context implementations
Context with Hook Custom hook that throws if outside provider Guaranteed non-null context, better DX Enforcing provider usage
Generic Context createContext<T> with generic Reusable context pattern Generic state containers
Context with Actions Separate state and dispatch Type-safe actions, better organization Complex state management
Multiple Contexts Compose multiple providers Separation of concerns Large apps with different domains

Example: Type-safe context with custom hook

// Define context type
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

// Create context with null default
const AuthContext = createContext<AuthContextType | null>(null);

// Provider component
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (email: string, password: string) => {
    // API call
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const userData = await response.json();
    setUser(userData);
  };

  const logout = () => {
    setUser(null);
  };

  const value: AuthContextType = {
    user,
    login,
    logout,
    isAuthenticated: user !== null,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

// Custom hook with type guard
export function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  
  return context;
}

// Usage - TypeScript knows context is never null
function Profile() {
  const { user, logout } = useAuth(); // No null check needed
  
  return (
    <div>
      <h1>{user?.name}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Example: Context with reducer pattern

// State and actions
interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

interface TodoState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
}

type TodoAction =
  | { type: 'ADD_TODO'; text: string }
  | { type: 'TOGGLE_TODO'; id: string }
  | { type: 'DELETE_TODO'; id: string }
  | { type: 'SET_FILTER'; filter: TodoState['filter'] };

// Reducer
function todoReducer(state: TodoState, action: TodoAction): TodoState {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          { id: Date.now().toString(), text: action.text, completed: false },
        ],
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
        ),
      };
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.id),
      };
    case 'SET_FILTER':
      return { ...state, filter: action.filter };
    default:
      return state;
  }
}

// Context types
interface TodoContextType {
  state: TodoState;
  dispatch: React.Dispatch<TodoAction>;
}

const TodoContext = createContext<TodoContextType | null>(null);

// Provider
export function TodoProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: 'all',
  });

  return (
    <TodoContext.Provider value={{ state, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
}

// Custom hooks
export function useTodos() {
  const context = useContext(TodoContext);
  if (!context) throw new Error('useTodos must be used within TodoProvider');
  return context;
}

// Helper hooks
export function useTodoActions() {
  const { dispatch } = useTodos();

  return {
    addTodo: (text: string) => dispatch({ type: 'ADD_TODO', text }),
    toggleTodo: (id: string) => dispatch({ type: 'TOGGLE_TODO', id }),
    deleteTodo: (id: string) => dispatch({ type: 'DELETE_TODO', id }),
    setFilter: (filter: TodoState['filter']) => dispatch({ type: 'SET_FILTER', filter }),
  };
}

// Usage
function TodoList() {
  const { state } = useTodos();
  const { toggleTodo, deleteTodo } = useTodoActions();

  return (
    <ul>
      {state.todos.map(todo => (
        <li key={todo.id}>
          <input 
            type="checkbox" 
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          {todo.text}
          <button onClick={() => deleteTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

21.5 Custom Hook Type Safety and Inference

Concept Pattern Benefit Example
Return Type Inference Let TypeScript infer return type Less verbose, auto-updates Hook without explicit return type
Explicit Return Type Define return type explicitly Better documentation, API contracts Public library hooks
Tuple Return return [value, setter] as const Array destructuring with types useState-like hooks
Generic Hooks function useHook<T>() Reusable with different types Generic data fetching, storage
Conditional Types Return type based on parameters Smart type inference Optional parameters affecting return
Type Guards Runtime checks with type narrowing Type safety with validation Parsing external data

Example: Custom hook with type inference

// Inferred return type
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => setCount(c => c + 1), []);
  const decrement = useCallback(() => setCount(c => c - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);
  
  // Return type inferred as { count: number; increment: () => void; ... }
  return { count, increment, decrement, reset };
}

// Tuple return with const assertion
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  const toggle = useCallback(() => setValue(v => !v), []);
  
  // 'as const' makes it a tuple, not array
  return [value, toggle] as const;
}

// Usage with proper types
const [isOpen, toggleOpen] = useToggle(); // isOpen: boolean, toggleOpen: () => void

// Generic hook with constraints
function useAsync<T, E = Error>(
  asyncFunction: () => Promise<T>,
  immediate = true
) {
  const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<E | null>(null);

  const execute = useCallback(async () => {
    setStatus('pending');
    setData(null);
    setError(null);

    try {
      const response = await asyncFunction();
      setData(response);
      setStatus('success');
      return response;
    } catch (err) {
      setError(err as E);
      setStatus('error');
      throw err;
    }
  }, [asyncFunction]);

  useEffect(() => {
    if (immediate) {
      execute();
    }
  }, [execute, immediate]);

  return { execute, status, data, error };
}

// Usage with type inference
interface User {
  id: number;
  name: string;
}

function UserProfile() {
  const { data: user, status } = useAsync<User>(
    () => fetch('/api/user').then(r => r.json()),
    true
  );
  
  // TypeScript knows user is User | null
  if (status === 'success' && user) {
    return <div>{user.name}</div>;
  }
  
  return <div>Loading...</div>;
}

Example: Advanced generic hooks

// Generic form hook
interface UseFormOptions<T> {
  initialValues: T;
  onSubmit: (values: T) => void | Promise<void>;
  validate?: (values: T) => Partial<Record<keyof T, string>>;
}

function useForm<T extends Record<string, any>>({
  initialValues,
  onSubmit,
  validate,
}: UseFormOptions<T>) {
  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = useCallback((name: keyof T, value: any) => {
    setValues(prev => ({ ...prev, [name]: value }));
    setErrors(prev => ({ ...prev, [name]: undefined }));
  }, []);

  const handleSubmit = useCallback(async (event?: React.FormEvent) => {
    event?.preventDefault();

    if (validate) {
      const validationErrors = validate(values);
      if (Object.keys(validationErrors).length > 0) {
        setErrors(validationErrors);
        return;
      }
    }

    setIsSubmitting(true);
    try {
      await onSubmit(values);
    } finally {
      setIsSubmitting(false);
    }
  }, [values, validate, onSubmit]);

  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
  }, [initialValues]);

  return { values, errors, isSubmitting, handleChange, handleSubmit, reset };
}

// Usage with type inference
interface LoginForm {
  email: string;
  password: string;
}

function Login() {
  const { values, errors, handleChange, handleSubmit } = useForm<LoginForm>({
    initialValues: { email: '', password: '' },
    onSubmit: async (values) => {
      await login(values.email, values.password);
    },
    validate: (values) => {
      const errors: Partial<Record<keyof LoginForm, string>> = {};
      if (!values.email) errors.email = 'Required';
      if (!values.password) errors.password = 'Required';
      return errors;
    },
  });

  // TypeScript knows values.email and values.password exist
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={values.email}
        onChange={(e) => handleChange('email', e.target.value)}
      />
      {errors.email && <span>{errors.email}</span>}
      
      <input
        type="password"
        value={values.password}
        onChange={(e) => handleChange('password', e.target.value)}
      />
      {errors.password && <span>{errors.password}</span>}
      
      <button type="submit">Login</button>
    </form>
  );
}

21.6 React.FC vs Function Component Types

Approach Syntax Pros Cons
React.FC const Comp: React.FC<Props> = ... Includes children type, shorter syntax Implicit children (not always wanted), verbose
Function Declaration function Comp(props: Props) {...} Explicit, clear, standard TypeScript Need to define children explicitly
Arrow Function const Comp = (props: Props) => ... Concise, explicit types No hoisting, slightly more verbose
With Generics function Comp<T>(props: Props<T>) {...} Type-safe generic components More complex, requires understanding generics

Example: React.FC vs function declaration

// ❌ React.FC (less recommended now)
interface ButtonProps {
  label: string;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ label, onClick, children }) => {
  // children is implicitly included (might not be wanted)
  return <button onClick={onClick}>{label}{children}</button>;
};

// ✅ Function declaration (recommended)
interface CardProps {
  title: string;
  children: React.ReactNode; // Explicit children
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

// ✅ Arrow function (also good)
interface AlertProps {
  message: string;
  type: 'info' | 'warning' | 'error';
}

const Alert = ({ message, type }: AlertProps) => {
  return <div className={`alert alert-${type}`}>{message}</div>;
};

// ✅ Function with explicit return type
function UserCard({ user }: { user: User }): JSX.Element {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// Generic component - function declaration
function List<T>({ items, renderItem }: {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Component with default props (function declaration)
interface BadgeProps {
  text: string;
  color?: 'red' | 'blue' | 'green';
  size?: 'small' | 'medium' | 'large';
}

function Badge({ text, color = 'blue', size = 'medium' }: BadgeProps) {
  return <span className={`badge badge-${color} badge-${size}`}>{text}</span>;
}

Example: Component type patterns

// Polymorphic component (advanced)
type AsProp<C extends React.ElementType> = {
  as?: C;
};

type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);

type PolymorphicComponentProp<
  C extends React.ElementType,
  Props = {}
> = React.PropsWithChildren<Props & AsProp<C>> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

type PolymorphicComponentPropWithRef<
  C extends React.ElementType,
  Props = {}
> = PolymorphicComponentProp<C, Props> & { ref?: PolymorphicRef<C> };

type PolymorphicRef<C extends React.ElementType> = 
  React.ComponentPropsWithRef<C>['ref'];

// Usage
interface TextProps {
  color?: 'primary' | 'secondary';
}

function Text<C extends React.ElementType = 'span'>({
  as,
  color = 'primary',
  children,
  ...props
}: PolymorphicComponentPropWithRef<C, TextProps>) {
  const Component = as || 'span';
  return (
    <Component className={`text-${color}`} {...props}>
      {children}
    </Component>
  );
}

// Can be used as any element
<Text>Default span</Text>
<Text as="h1">Heading</Text>
<Text as="a" href="/link">Link</Text> // href is type-safe!

// Component with display name
const MyComponent = ({ name }: { name: string }) => {
  return <div>{name}</div>;
};

MyComponent.displayName = 'MyComponent'; // For React DevTools

// Component with static methods
interface TabsComponent {
  ({ children }: { children: React.ReactNode }): JSX.Element;
  Panel: ({ children }: { children: React.ReactNode }) => JSX.Element;
}

const Tabs: TabsComponent = ({ children }) => {
  return <div className="tabs">{children}</div>;
};

Tabs.Panel = ({ children }) => {
  return <div className="tab-panel">{children}</div>;
};

// Usage: <Tabs><Tabs.Panel>Content</Tabs.Panel></Tabs>
TypeScript Best Practices: Prefer function declarations over React.FC, explicitly define children prop when needed, use generics for reusable components, leverage type inference when possible, use discriminated unions for variant props, add explicit return types for public APIs, use const assertions for tuple returns, validate external data with runtime checks.

React TypeScript Integration Summary

22. React Performance Monitoring and Optimization

22.1 React Profiler API and Performance Metrics

Feature API Metrics Captured Use Case
Profiler Component <Profiler id="..." onRender={...}> Render time, phase, actual duration Component-level performance tracking
onRender Callback onRender(id, phase, actualDuration, ...) All render metrics per component tree Collect performance data programmatically
Interaction Tracing DevTools Profiler with interactions User interactions causing renders Debug performance bottlenecks
Commit Phase Timing baseDuration vs actualDuration Optimization impact measurement Measure memo/callback effectiveness
Custom Metrics Performance API integration Custom timing, user timing marks Business-specific metrics
Production Profiling React with profiling build Real user performance data Production performance monitoring

Example: React Profiler API usage

import { Profiler, ProfilerOnRenderCallback } from 'react';

// Profiler callback
const onRenderCallback: ProfilerOnRenderCallback = (
  id,                    // Profiler id
  phase,                 // "mount" or "update"
  actualDuration,        // Time spent rendering
  baseDuration,          // Estimated time without memoization
  startTime,             // When React began rendering
  commitTime,            // When React committed the update
  interactions           // Set of interactions for this update
) => {
  console.log(`Profiler [${id}] - ${phase}`);
  console.log(`Actual duration: ${actualDuration}ms`);
  console.log(`Base duration: ${baseDuration}ms`);
  console.log(`Time saved: ${baseDuration - actualDuration}ms`);
  
  // Send to analytics
  if (actualDuration > 16) { // More than one frame
    sendToAnalytics({
      component: id,
      phase,
      duration: actualDuration,
      timestamp: commitTime,
    });
  }
};

// Wrap component tree with Profiler
function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Header />
      <Profiler id="Navigation" onRender={onRenderCallback}>
        <Navigation />
      </Profiler>
      <Profiler id="Content" onRender={onRenderCallback}>
        <MainContent />
      </Profiler>
      <Footer />
    </Profiler>
  );
}

// Custom performance tracking hook
function usePerformanceTracking(componentName: string) {
  useEffect(() => {
    const startMark = `${componentName}-start`;
    const endMark = `${componentName}-end`;
    const measureName = `${componentName}-render`;

    performance.mark(startMark);

    return () => {
      performance.mark(endMark);
      performance.measure(measureName, startMark, endMark);
      
      const measure = performance.getEntriesByName(measureName)[0];
      console.log(`${componentName} render time: ${measure.duration}ms`);
      
      // Clean up
      performance.clearMarks(startMark);
      performance.clearMarks(endMark);
      performance.clearMeasures(measureName);
    };
  });
}

// Usage in component
function ExpensiveComponent() {
  usePerformanceTracking('ExpensiveComponent');
  
  // Component logic
  return <div>...</div>;
}

Example: Production performance monitoring

// Performance monitoring service
class PerformanceMonitor {
  private metrics: Map<string, number[]> = new Map();

  recordMetric(name: string, duration: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name)!.push(duration);
  }

  getStats(name: string) {
    const durations = this.metrics.get(name) || [];
    if (durations.length === 0) return null;

    const sorted = [...durations].sort((a, b) => a - b);
    return {
      count: durations.length,
      avg: durations.reduce((a, b) => a + b, 0) / durations.length,
      median: sorted[Math.floor(sorted.length / 2)],
      p95: sorted[Math.floor(sorted.length * 0.95)],
      p99: sorted[Math.floor(sorted.length * 0.99)],
      min: sorted[0],
      max: sorted[sorted.length - 1],
    };
  }

  flush() {
    const stats: Record<string, any> = {};
    this.metrics.forEach((_, name) => {
      stats[name] = this.getStats(name);
    });
    
    // Send to backend
    fetch('/api/performance', {
      method: 'POST',
      body: JSON.stringify(stats),
    });
    
    this.metrics.clear();
  }
}

const monitor = new PerformanceMonitor();

// Global profiler callback
const globalProfilerCallback: ProfilerOnRenderCallback = (
  id,
  phase,
  actualDuration
) => {
  monitor.recordMetric(`${id}-${phase}`, actualDuration);
};

// Flush metrics periodically
setInterval(() => {
  monitor.flush();
}, 60000); // Every minute

// Web Vitals integration
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';

function sendToAnalytics({ name, delta, id }: any) {
  fetch('/api/vitals', {
    method: 'POST',
    body: JSON.stringify({ name, value: delta, id }),
  });
}

onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);

22.2 Bundle Analysis and Code Splitting Strategies

Strategy Implementation Bundle Impact Best For
Route-based Splitting React.lazy(() => import('./Route')) Split by page/route Multi-page applications
Component-based Splitting Lazy load heavy components Split by feature/component Large components, modals, charts
Library Splitting Dynamic imports for large libs Vendor bundle optimization Heavy libraries (PDF, charts, maps)
Webpack Bundle Analyzer webpack-bundle-analyzer Visualize bundle composition Identify optimization opportunities
Tree Shaking ES6 modules, side-effect-free Remove unused exports Reduce bundle size automatically
Prefetching import(/* webpackPrefetch: true */) Load during idle time Improve perceived performance

Example: Advanced code splitting patterns

// Route-based code splitting
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// Component-based splitting with prefetch
function ProductList() {
  const [showModal, setShowModal] = useState(false);
  
  // Lazy load modal component
  const ProductModal = lazy(() => import('./ProductModal'));

  // Prefetch on hover
  const handleMouseEnter = () => {
    import('./ProductModal'); // Prefetch
  };

  return (
    <div>
      <button 
        onClick={() => setShowModal(true)}
        onMouseEnter={handleMouseEnter}
      >
        View Details
      </button>
      
      {showModal && (
        <Suspense fallback={<Spinner />}>
          <ProductModal onClose={() => setShowModal(false)} />
        </Suspense>
      )}
    </div>
  );
}

// Library splitting - heavy chart library
const Chart = lazy(() => import('recharts').then(module => ({
  default: module.LineChart
})));

function Analytics() {
  return (
    <Suspense fallback={<ChartSkeleton />}>
      <Chart data={data} />
    </Suspense>
  );
}

// Dynamic import with error handling
function DynamicComponent() {
  const [Component, setComponent] = useState<React.ComponentType | null>(null);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    import('./HeavyComponent')
      .then(module => setComponent(() => module.default))
      .catch(err => setError(err));
  }, []);

  if (error) return <ErrorMessage error={error} />;
  if (!Component) return <Loading />;

  return <Component />;
}

// Webpack magic comments for optimization
const AdminPanel = lazy(() =>
  import(
    /* webpackChunkName: "admin" */
    /* webpackPrefetch: true */
    './pages/AdminPanel'
  )
);

const ReportGenerator = lazy(() =>
  import(
    /* webpackChunkName: "reports" */
    /* webpackPreload: true */
    './components/ReportGenerator'
  )
);

Example: Bundle analysis and optimization

// package.json scripts for bundle analysis
{
  "scripts": {
    "analyze": "webpack-bundle-analyzer build/bundle-stats.json",
    "build:analyze": "npm run build -- --stats && npm run analyze"
  }
}

// Webpack configuration for bundle optimization
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // Vendor bundle
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
        },
        // Common components
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true,
        },
        // React core
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          priority: 20,
        },
      },
    },
    runtimeChunk: 'single', // Extract runtime into separate chunk
  },
};

// Analyze imports and suggest optimizations
import { lazy } from 'react';

// ❌ Bad: Import entire library
import _ from 'lodash';
import moment from 'moment';

// ✅ Good: Import only what you need
import debounce from 'lodash/debounce';
import dayjs from 'dayjs'; // Lighter alternative

// ❌ Bad: Import all icons
import * as Icons from 'react-icons/fa';

// ✅ Good: Import specific icons
import { FaUser, FaHome } from 'react-icons/fa';

// Tree shaking configuration in package.json
{
  "sideEffects": [
    "*.css",
    "*.scss"
  ]
}

// Check bundle size with size-limit
// size-limit.config.js
module.exports = [
  {
    name: 'Main bundle',
    path: 'build/static/js/*.js',
    limit: '200 KB',
  },
  {
    name: 'Vendor bundle',
    path: 'build/static/js/vendors.*.js',
    limit: '150 KB',
  },
];

22.3 Memory Leak Detection and Prevention

Leak Source Detection Method Prevention Tools
Event Listeners Memory snapshots, heap profiling Remove in cleanup function Chrome DevTools Memory Profiler
Timers/Intervals Check running timers count Clear in useEffect cleanup React DevTools Profiler
Subscriptions Monitor active subscriptions Unsubscribe in cleanup Custom logging
DOM References Detached DOM tree analysis Clear refs, remove listeners Chrome Memory Profiler
State Updates Warning: setState on unmounted Cancel async operations React strict mode warnings
Closures Heap snapshot comparison Avoid large closure scopes Memory timeline recording

Example: Memory leak prevention patterns

// ✅ Proper event listener cleanup
function WindowResizeComponent() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    
    window.addEventListener('resize', handleResize);
    
    // Cleanup: Remove listener
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <div>Width: {width}</div>;
}

// ✅ Timer cleanup
function CountdownTimer() {
  const [count, setCount] = useState(60);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c - 1);
    }, 1000);

    // Cleanup: Clear interval
    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return <div>{count}s remaining</div>;
}

// ✅ Abort controller for fetch requests
function DataFetcher({ id }: { id: string }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();

    async function fetchData() {
      try {
        const response = await fetch(`/api/data/${id}`, {
          signal: abortController.signal,
        });
        const json = await response.json();
        setData(json);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        }
      }
    }

    fetchData();

    // Cleanup: Abort pending request
    return () => {
      abortController.abort();
    };
  }, [id]);

  return <div>{JSON.stringify(data)}</div>;
}

// ✅ WebSocket cleanup
function WebSocketComponent() {
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    const ws = new WebSocket('ws://localhost:8080');

    ws.onmessage = (event) => {
      setMessages(prev => [...prev, event.data]);
    };

    // Cleanup: Close connection
    return () => {
      ws.close();
    };
  }, []);

  return <ul>{messages.map((msg, i) => <li key={i}>{msg}</li>)}</ul>;
}

// ✅ Observable/RxJS subscription cleanup
import { interval } from 'rxjs';

function ObservableComponent() {
  const [tick, setTick] = useState(0);

  useEffect(() => {
    const subscription = interval(1000).subscribe(val => {
      setTick(val);
    });

    // Cleanup: Unsubscribe
    return () => {
      subscription.unsubscribe();
    };
  }, []);

  return <div>Tick: {tick}</div>;
}

Example: Memory leak detection utilities

// Custom hook to detect memory leaks
function useMemoryLeakDetector(componentName: string) {
  useEffect(() => {
    const mountTime = Date.now();
    console.log(`[${componentName}] Mounted`);

    return () => {
      const unmountTime = Date.now();
      const lifetime = unmountTime - mountTime;
      console.log(`[${componentName}] Unmounted after ${lifetime}ms`);

      // Check for detached listeners
      if (process.env.NODE_ENV === 'development') {
        setTimeout(() => {
          // This will warn if state updates after unmount
          console.log(`[${componentName}] Cleanup verification`);
        }, 100);
      }
    };
  }, [componentName]);
}

// Safe async state updates
function useSafeState<T>(initialState: T) {
  const [state, setState] = useState(initialState);
  const isMountedRef = useRef(true);

  useEffect(() => {
    return () => {
      isMountedRef.current = false;
    };
  }, []);

  const setSafeState = useCallback((value: T | ((prev: T) => T)) => {
    if (isMountedRef.current) {
      setState(value);
    }
  }, []);

  return [state, setSafeState] as const;
}

// Usage
function AsyncComponent() {
  const [data, setData] = useSafeState(null);

  useEffect(() => {
    fetchData().then(result => {
      setData(result); // Safe even if unmounted
    });
  }, [setData]);

  return <div>{JSON.stringify(data)}</div>;
}

// Memory profiling helper
class MemoryProfiler {
  private snapshots: number[] = [];

  takeSnapshot() {
    if (performance.memory) {
      this.snapshots.push(performance.memory.usedJSHeapSize);
      console.log(`Heap size: ${(performance.memory.usedJSHeapSize / 1048576).toFixed(2)} MB`);
    }
  }

  analyze() {
    if (this.snapshots.length < 2) return;

    const growth = this.snapshots[this.snapshots.length - 1] - this.snapshots[0];
    const avgGrowth = growth / this.snapshots.length;

    console.log(`Total growth: ${(growth / 1048576).toFixed(2)} MB`);
    console.log(`Average growth per snapshot: ${(avgGrowth / 1048576).toFixed(2)} MB`);
  }
}

const profiler = new MemoryProfiler();

// Take snapshots periodically
setInterval(() => profiler.takeSnapshot(), 5000);

22.4 Re-render Analysis and Optimization

Technique Implementation Impact When to Use
React.memo Wrap component with memo Skip re-render if props unchanged Pure components with expensive renders
useMemo Memoize expensive calculations Cache computed values Heavy computations, derived data
useCallback Memoize function references Stable function identity Callbacks passed to memo'd children
Context Splitting Separate contexts by update frequency Reduce context re-render scope Large context with mixed data
Component Splitting Break into smaller components Isolate re-renders Large components with mixed state
Virtualization Render only visible items Massive performance gain for lists Long lists, tables, grids

Example: Re-render optimization patterns

// ✅ React.memo with custom comparison
interface ItemProps {
  item: { id: string; name: string; price: number };
  onSelect: (id: string) => void;
}

const ListItem = memo(({ item, onSelect }: ItemProps) => {
  console.log(`Rendering item: ${item.id}`);
  
  return (
    <div onClick={() => onSelect(item.id)}>
      {item.name} - ${item.price}
    </div>
  );
}, (prevProps, nextProps) => {
  // Custom comparison: only re-render if item changed
  return prevProps.item.id === nextProps.item.id &&
         prevProps.item.name === nextProps.item.name &&
         prevProps.item.price === nextProps.item.price;
});

// ✅ useCallback for stable function reference
function ProductList() {
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [items, setItems] = useState(products);

  // Memoize callback to prevent ListItem re-renders
  const handleSelect = useCallback((id: string) => {
    setSelectedId(id);
  }, []);

  return (
    <div>
      {items.map(item => (
        <ListItem key={item.id} item={item} onSelect={handleSelect} />
      ))}
    </div>
  );
}

// ✅ useMemo for expensive calculations
function DataTable({ data }: { data: DataPoint[] }) {
  // Only recalculate when data changes
  const stats = useMemo(() => {
    console.log('Calculating stats...');
    return {
      total: data.reduce((sum, d) => sum + d.value, 0),
      average: data.reduce((sum, d) => sum + d.value, 0) / data.length,
      max: Math.max(...data.map(d => d.value)),
      min: Math.min(...data.map(d => d.value)),
    };
  }, [data]);

  return (
    <div>
      <div>Total: {stats.total}</div>
      <div>Average: {stats.average}</div>
      <div>Max: {stats.max}</div>
      <div>Min: {stats.min}</div>
    </div>
  );
}

// ✅ Context splitting to reduce re-renders
// Bad: Single context causes all consumers to re-render
const AppContext = createContext({
  user: null,
  theme: 'light',
  settings: {},
  notifications: [],
});

// Good: Split into separate contexts
const UserContext = createContext(null);
const ThemeContext = createContext('light');
const SettingsContext = createContext({});
const NotificationsContext = createContext([]);

// Components only re-render when their specific context changes
function UserProfile() {
  const user = useContext(UserContext); // Only re-renders on user change
  return <div>{user?.name}</div>;
}

function ThemeSwitcher() {
  const theme = useContext(ThemeContext); // Only re-renders on theme change
  return <button>{theme}</button>;
}

Example: Advanced re-render debugging

// Custom hook to track re-renders
function useRenderCount(componentName: string) {
  const renderCount = useRef(0);
  
  useEffect(() => {
    renderCount.current += 1;
    console.log(`[${componentName}] Render count: ${renderCount.current}`);
  });
  
  return renderCount.current;
}

// Hook to track prop changes
function useWhyDidYouUpdate(name: string, props: Record<string, any>) {
  const previousProps = useRef<Record<string, any>>();

  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps: Record<string, any> = {};

      allKeys.forEach(key => {
        if (previousProps.current![key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current![key],
            to: props[key],
          };
        }
      });

      if (Object.keys(changedProps).length > 0) {
        console.log('[why-did-you-update]', name, changedProps);
      }
    }

    previousProps.current = props;
  });
}

// Usage in component
function ExpensiveComponent({ data, config, onUpdate }: Props) {
  const renderCount = useRenderCount('ExpensiveComponent');
  useWhyDidYouUpdate('ExpensiveComponent', { data, config, onUpdate });

  return <div>Rendered {renderCount} times</div>;
}

// Component splitting for optimization
// ❌ Bad: Entire form re-renders on any input change
function BadForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });

  return (
    <div>
      <input 
        value={formData.name}
        onChange={e => setFormData({...formData, name: e.target.value})}
      />
      <input 
        value={formData.email}
        onChange={e => setFormData({...formData, email: e.target.value})}
      />
      <ExpensivePreview data={formData} />
    </div>
  );
}

// ✅ Good: Split into controlled components
const FormInput = memo(({ value, onChange, name }: any) => {
  console.log(`Rendering input: ${name}`);
  return <input value={value} onChange={onChange} />;
});

function GoodForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const formData = useMemo(() => ({ name, email }), [name, email]);

  return (
    <div>
      <FormInput name="name" value={name} onChange={e => setName(e.target.value)} />
      <FormInput name="email" value={email} onChange={e => setEmail(e.target.value)} />
      <ExpensivePreview data={formData} />
    </div>
  );
}

22.5 Core Web Vitals and React Performance

Metric Target React Impact Optimization Strategy
LCP (Largest Contentful Paint) < 2.5s Initial render performance Code splitting, SSR, image optimization
FID (First Input Delay) < 100ms JavaScript execution time Reduce bundle size, defer non-critical JS
CLS (Cumulative Layout Shift) < 0.1 Dynamic content insertion Reserve space, avoid layout shifts
FCP (First Contentful Paint) < 1.8s Time to first render Minimize initial bundle, critical CSS
TTFB (Time to First Byte) < 600ms Server response time SSR optimization, CDN, caching
TBT (Total Blocking Time) < 200ms Long tasks blocking main thread Code splitting, Web Workers

Example: Core Web Vitals optimization

// LCP optimization - prioritize hero image
function HeroSection() {
  return (
    <section>
      <img
        src="/hero-large.jpg"
        alt="Hero"
        // Prioritize loading
        loading="eager"
        fetchpriority="high"
        // Prevent layout shift
        width={1200}
        height={600}
      />
    </section>
  );
}

// FID optimization - defer non-critical code
function App() {
  const [analyticsLoaded, setAnalyticsLoaded] = useState(false);

  useEffect(() => {
    // Load analytics after initial render
    requestIdleCallback(() => {
      import('./analytics').then(module => {
        module.initAnalytics();
        setAnalyticsLoaded(true);
      });
    });
  }, []);

  return <MainApp />;
}

// CLS optimization - prevent layout shifts
function ImageWithPlaceholder({ src, alt }: { src: string; alt: string }) {
  const [loaded, setLoaded] = useState(false);

  return (
    <div style={{ position: 'relative', width: 300, height: 200 }}>
      {/* Placeholder reserves space */}
      {!loaded && (
        <div 
          style={{ 
            position: 'absolute', 
            inset: 0, 
            backgroundColor: '#f0f0f0' 
          }} 
        />
      )}
      <img
        src={src}
        alt={alt}
        width={300}
        height={200}
        onLoad={() => setLoaded(true)}
        style={{ display: loaded ? 'block' : 'none' }}
      />
    </div>
  );
}

// Measure Core Web Vitals
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';

function reportWebVitals() {
  const sendToAnalytics = ({ name, value, id }: any) => {
    // Send to analytics endpoint
    fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify({
        metric: name,
        value: Math.round(name === 'CLS' ? value * 1000 : value),
        id,
        timestamp: Date.now(),
      }),
      keepalive: true,
    });
  };

  onCLS(sendToAnalytics);
  onFID(sendToAnalytics);
  onLCP(sendToAnalytics);
  onFCP(sendToAnalytics);
  onTTFB(sendToAnalytics);
}

// Initialize in app
useEffect(() => {
  reportWebVitals();
}, []);

Example: React performance best practices

// Optimize initial load with React.lazy
const HeavyChart = lazy(() =>
  import(/* webpackChunkName: "chart" */ './HeavyChart')
);

function Dashboard() {
  return (
    <div>
      <Header /> {/* Render immediately */}
      <Suspense fallback={<ChartSkeleton />}>
        <HeavyChart /> {/* Load after initial render */}
      </Suspense>
    </div>
  );
}

// Prevent CLS with skeleton screens
function SkeletonCard() {
  return (
    <div 
      style={{ 
        width: '300px', 
        height: '200px', 
        backgroundColor: '#f0f0f0',
        borderRadius: '8px',
        animation: 'pulse 1.5s infinite',
      }}
    />
  );
}

function ProductCard({ id }: { id: string }) {
  const { data, loading } = useFetch<Product>(`/api/products/${id}`);

  if (loading) return <SkeletonCard />; // Same dimensions as actual card

  return (
    <div style={{ width: '300px', height: '200px' }}>
      <img src={data.image} alt={data.name} />
      <h3>{data.name}</h3>
    </div>
  );
}

// Optimize TBT with progressive hydration
function ProgressiveApp() {
  const [hydrated, setHydrated] = useState({
    header: false,
    content: false,
    footer: false,
  });

  useEffect(() => {
    // Hydrate header first
    setHydrated(prev => ({ ...prev, header: true }));
    
    requestIdleCallback(() => {
      // Hydrate content when idle
      setHydrated(prev => ({ ...prev, content: true }));
      
      requestIdleCallback(() => {
        // Hydrate footer last
        setHydrated(prev => ({ ...prev, footer: true }));
      });
    });
  }, []);

  return (
    <div>
      {hydrated.header ? <Header /> : <HeaderSSR />}
      {hydrated.content ? <Content /> : <ContentSSR />}
      {hydrated.footer ? <Footer /> : <FooterSSR />}
    </div>
  );
}

22.6 Production Performance Monitoring Tools

Tool Features Use Case Integration
Sentry Error tracking, performance monitoring, tracing Production error tracking, performance issues React SDK, automatic error boundaries
New Relic APM, browser monitoring, distributed tracing Full-stack performance monitoring Browser agent, React instrumentation
Datadog RUM Real user monitoring, session replay, metrics User experience analytics Browser SDK, React integration
LogRocket Session replay, performance monitoring, logs Debug production issues with replays React SDK, Redux integration
Lighthouse CI Automated performance testing, CI/CD integration Performance regression prevention GitHub Actions, GitLab CI
Web Vitals Library Measure Core Web Vitals in production Track real user metrics NPM package, analytics integration

Example: Production monitoring setup

// Sentry integration
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  integrations: [
    new BrowserTracing(),
    new Sentry.Replay({
      maskAllText: true,
      blockAllMedia: true,
    }),
  ],
  
  // Performance monitoring
  tracesSampleRate: 0.1, // 10% of transactions
  
  // Session replay
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  
  // Environment
  environment: process.env.NODE_ENV,
  
  // Release tracking
  release: process.env.REACT_APP_VERSION,
  
  // Custom tags
  beforeSend(event, hint) {
    // Add custom context
    event.tags = {
      ...event.tags,
      'user.role': getCurrentUserRole(),
    };
    return event;
  },
});

// Wrap app with Sentry error boundary
function App() {
  return (
    <Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
      <Router />
    </Sentry.ErrorBoundary>
  );
}

// Track custom performance metrics
function trackCustomMetric(name: string, value: number) {
  Sentry.metrics.distribution(name, value, {
    unit: 'millisecond',
    tags: { page: window.location.pathname },
  });
}

// Component performance tracking
function ExpensiveComponent() {
  useEffect(() => {
    const startTime = performance.now();
    
    return () => {
      const duration = performance.now() - startTime;
      trackCustomMetric('ExpensiveComponent.renderTime', duration);
    };
  });
  
  return <div>...</div>;
}

Example: Multi-tool monitoring setup

// LogRocket setup
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

LogRocket.init('YOUR_APP_ID', {
  release: process.env.REACT_APP_VERSION,
  network: {
    requestSanitizer: request => {
      // Sanitize sensitive data
      if (request.headers['Authorization']) {
        request.headers['Authorization'] = '[REDACTED]';
      }
      return request;
    },
  },
});

setupLogRocketReact(LogRocket);

// Connect to Sentry
LogRocket.getSessionURL(sessionURL => {
  Sentry.configureScope(scope => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// New Relic Browser Agent
// Add to index.html or initialize programmatically
<script src="https://js-agent.newrelic.com/nr-loader.js"></script>
<script>
  window.NREUM.init = {
    applicationID: 'YOUR_APP_ID',
    licenseKey: 'YOUR_LICENSE_KEY',
    // Additional config
  };
</script>

// Datadog RUM
import { datadogRum } from '@datadog/browser-rum';

datadogRum.init({
  applicationId: 'YOUR_APP_ID',
  clientToken: 'YOUR_CLIENT_TOKEN',
  site: 'datadoghq.com',
  service: 'react-app',
  env: process.env.NODE_ENV,
  version: process.env.REACT_APP_VERSION,
  sessionSampleRate: 100,
  sessionReplaySampleRate: 20,
  trackUserInteractions: true,
  trackResources: true,
  trackLongTasks: true,
  defaultPrivacyLevel: 'mask-user-input',
});

// Start session replay
datadogRum.startSessionReplayRecording();

// Custom action tracking
function trackUserAction(name: string, context?: Record<string, any>) {
  datadogRum.addAction(name, context);
  LogRocket.track(name, context);
  Sentry.addBreadcrumb({
    message: name,
    data: context,
  });
}

// Web Vitals to all platforms
import { onCLS, onFID, onLCP } from 'web-vitals';

function sendToAllPlatforms(metric: any) {
  // Send to Sentry
  Sentry.metrics.distribution(metric.name, metric.value);
  
  // Send to Datadog
  datadogRum.addTiming(metric.name, metric.value);
  
  // Send to custom analytics
  fetch('/api/vitals', {
    method: 'POST',
    body: JSON.stringify(metric),
    keepalive: true,
  });
}

onCLS(sendToAllPlatforms);
onFID(sendToAllPlatforms);
onLCP(sendToAllPlatforms);

// Lighthouse CI configuration
// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      startServerCommand: 'npm run serve',
      url: ['http://localhost:3000'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['error', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};
Performance Monitoring Best Practices: Use React Profiler API for component-level insights, implement code splitting at route and component levels, detect and prevent memory leaks with proper cleanup, optimize re-renders with memo/useMemo/useCallback, monitor Core Web Vitals in production, integrate multiple monitoring tools for comprehensive coverage, set up automated performance testing in CI/CD, track custom business metrics, sanitize sensitive data in monitoring tools, establish performance budgets.

React Performance Monitoring and Optimization Summary