Back to Blog

Top 30 React Interview Questions and Answers (2026)

The most frequently asked React interview questions with detailed answers, code examples, and expert tips to help you land your dream frontend role.

Top 30 React Interview Questions and Answers (2026)

The ultimate cheat sheet for React developer interviews — from fundamentals to advanced patterns

1. What is React and why is it used?

React is an open-source JavaScript library developed by Meta (Facebook) for building user interfaces, particularly single-page applications (SPAs). It allows developers to create large web applications that can change data without reloading the page.

Why use React?

  • Component-based architecture: Build encapsulated components that manage their own state, then compose them to make complex UIs
  • Virtual DOM: Efficiently updates and renders only the components that change, improving performance
  • Declarative: Describe what the UI should look like for each state, and React handles the DOM updates
  • Unidirectional data flow: Data flows one way (parent to child), making state changes predictable and debuggable
  • Rich ecosystem: Massive community, thousands of libraries, and strong tooling support
  • React Native: Same paradigm for building mobile apps on iOS and Android

Interview tip: Don't just say "it's a JavaScript framework." React is a library, not a framework. It handles the view layer and can be augmented with other libraries for routing, state management, etc.

2. What is the Virtual DOM?

The Virtual DOM is a lightweight JavaScript representation of the actual DOM (Document Object Model). It acts as a copy of the real DOM kept in memory and is used by React to optimize rendering performance.

How it works (the Reconciliation process):

  1. When state changes, React creates a new Virtual DOM tree
  2. React compares (diffs) the new Virtual DOM with the previous one
  3. React identifies the minimal set of changes needed
  4. Only those specific changes are applied to the real DOM
// React batches updates and only touches the real DOM where needed
// Without Virtual DOM: re-render entire list on every change
// With Virtual DOM: only re-render the specific item that changed

// This is why React is fast — it minimizes expensive DOM operations
// Real DOM manipulation is 10-100x slower than JavaScript operations

Key insight: The Virtual DOM isn't inherently faster than the real DOM. React's advantage is in knowing when to update and what to update, minimizing unnecessary DOM manipulations.

3. What are Components in React?

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

Functional Components (Modern Approach)

// Functional Component with props
function UserCard({ name, email, avatar }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
}

// Arrow function variant
const UserCard = ({ name, email, avatar }) => (
  <div className="user-card">
    <img src={avatar} alt={name} />
    <h3>{name}</h3>
    <p>{email}</p>
  </div>
);

Class Components (Legacy)

import { Component } from 'react';

class UserCard extends Component {
  render() {
    const { name, email, avatar } = this.props;
    return (
      <div className="user-card">
        <img src={avatar} alt={name} />
        <h3>{name}</h3>
        <p>{email}</p>
      </div>
    );
  }
}

Best practice: Always use functional components with Hooks. Class components are still valid but are considered legacy. Functional components are simpler, easier to test, and have better performance.

4. What is JSX?

JSX (JavaScript XML) is a syntax extension that lets you write HTML-like code inside JavaScript. It makes creating React element trees more readable and expressive.

// JSX is syntactic sugar for React.createElement()
// JSX:
const element = <h1 className="title">Hello, {name}!</h1>

// Equivalent without JSX:
const element = React.createElement(
  'h1',
  { className: 'title' },
  `Hello, ${name}!`
);

// JSX supports expressions inside curly braces {}
const greeting = <p>Current time: {new Date().toLocaleTimeString()}</p>

// JSX supports conditional rendering
const display = <div>{isLoggedIn ? <LogoutButton /> : <LoginButton />}</div>

// JSX supports mapping arrays
const list = (
  <ul>
    {items.map(item => <li key={item.id}>{item.name}</li>)}
  </ul>
);

JSX Rules

  • Return a single root element (use Fragment <>...</> if needed)
  • All tags must be closed (including self-closing like <img />)
  • Use className instead of class
  • Use htmlFor instead of for
  • Inline styles use objects: style={{ color: 'red' }}

5. Props vs State

Understanding the difference between props and state is fundamental to React development.

Feature Props State
Definition Read-only inputs passed from parent to child Mutable data managed within the component
Mutability Immutable — cannot be modified by the receiving component Mutable — updated via setState or useState
Source Parent component Component itself
Triggers re-render? Only when parent re-renders Yes, whenever state changes
Use case Configuration, data passing, callbacks Interactivity, dynamic data, form inputs
// Props: passed from parent
function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;
}

// Child receives props (read-only)
function Child({ count, onIncrement }) {
  // Props cannot be modified: this.props.count++ // WRONG
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onIncrement}>+1</button>
    </div>
  );
}

// State: managed internally
function Counter() {
  const [count, setCount] = useState(0); // state
  return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}

6. What are React Hooks?

Hooks are functions that let you "hook into" React state and lifecycle features from functional components. They were introduced in React 16.8 to eliminate the need for class components.

Rules of Hooks

  • Only call Hooks at the top level — never inside loops, conditions, or nested functions
  • Only call Hooks from React functions — either React function components or custom Hooks
// ✅ Correct: Hooks at top level
function MyComponent() {
  const [count, setCount] = useState(0);
  const ref = useRef(null);

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return <button ref={ref}>{count}</button>;
}

// ❌ Wrong: Hook inside condition
function MyComponent({ shouldFetch }) {
  if (shouldFetch) {
    const [data, setData] = useState(null); // ❌ WRONG!
  }
}

7. useState Hook

useState is the most basic Hook. It declares a state variable that persists between re-renders.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  // count: current state value
  // setCount: function to update state

  // Functional update (recommended when new state depends on old state)
  const increment = () => setCount(prev => prev + 1);

  // Object state
  const [user, setUser] = useState({ name: '', email: '' });

  const updateUser = () => {
    setUser(prev => ({ ...prev, name: 'John' })); // spread previous state
    // setUser({ name: 'John' }); // ❌ WRONG: loses email field
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <input
        value={user.name}
        onChange={(e) => setUser(prev => ({ ...prev, name: e.target.value }))}
      />
    </div>
  );
}

Common mistake: Always use the functional updater form setCount(prev => prev + 1) when the new state depends on the previous state. Direct access setCount(count + 1) can cause stale state bugs due to closures.

8. useEffect Hook

useEffect lets you perform side effects in functional components — data fetching, subscriptions, DOM manipulation, timers, and more.

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // Runs after every render where userId changes
  useEffect(() => {
    let cancelled = false; // cleanup flag

    const fetchUser = async () => {
      setLoading(true);
      try {
        const res = await fetch(`/api/users/${userId}`);
        const data = await res.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        console.error(err);
      } finally {
        if (!cancelled) setLoading(false);
      }
    };

    fetchUser();

    // Cleanup function: runs before next effect or on unmount
    return () => { cancelled = true; };
  }, [userId]); // dependency array

  if (loading) return <Spinner />;
  return <div>{user?.name}</div>;
}

// useEffect with different dependency patterns:
useEffect(() => {
  // Runs after EVERY render (no dependency array)
});

useEffect(() => {
  // Runs only ONCE on mount (empty dependency array)
}, []);

useEffect(() => {
  // Runs when 'userId' changes
}, [userId]);

useEffect(() => {
  // Runs when 'userId' OR 'theme' changes
}, [userId, theme]);

Common useEffect Patterns

Pattern Dependency Array When It Runs
On every render Not provided After every render
On mount only [] Once after initial render
On value change [value] After render when value changes
Cleanup only N/A Return function runs before next effect

9. useRef Hook

useRef creates a mutable reference object whose .current property persists across renders without triggering re-renders when changed.

Use Cases

  • Accessing DOM elements directly
  • Storing previous values
  • Holding mutable values that don't cause re-renders
  • Storing timer IDs
import { useRef, useEffect, useState } from 'react';

function TextInput() {
  const inputRef = useRef(null);
  const prevCountRef = useRef(0);
  const [count, setCount] = useState(0);

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

  // Track previous count without re-rendering
  useEffect(() => {
    prevCountRef.current = count;
  }, [count]);

  return (
    <div>
      <input ref={inputRef} placeholder="Type here..." />
      <p>Current: {count}, Previous: {prevCountRef.current}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

Key difference from useState: Changing a useRef value does NOT cause a re-render. Use useRef for values that need to persist but shouldn't trigger UI updates.

10. useMemo vs useCallback

Both are performance optimization hooks, but they serve different purposes.

Feature useMemo useCallback
Purpose Memoizes a computed value Memoizes a function
Returns The memoized value The memoized function
Use case Expensive calculations Stable function references for child components
Equivalent to useCallback(() => value, [deps]) useMemo(() => fn, [deps])
import { useMemo, useCallback, useState } from 'react';

function ProductList({ products }) {
  const [filter, setFilter] = useState('');
  const [sortOrder, setSortOrder] = useState('asc');

  // useMemo: expensive calculation — only re-runs when dependencies change
  const filteredProducts = useMemo(() => {
    console.log('Filtering products...'); // only logs when filter/sort changes
    return products
      .filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
      .sort((a, b) => sortOrder === 'asc' ? a.price - b.price : b.price - a.price);
  }, [products, filter, sortOrder]);

  // useCallback: stable function reference — prevents child re-renders
  const handleAddToCart = useCallback((productId) => {
    console.log(`Adding product ${productId} to cart`);
  }, []);

  return (
    <div>
      <input value={filter} onChange={(e) => setFilter(e.target.value)} />
      {filteredProducts.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          onAddToCart={handleAddToCart}
        />
      ))}
    </div>
  );
}

Don't over-optimize: Only use useMemo and useCallback when you've identified a performance bottleneck. Premature optimization adds complexity without benefit.

11. useReducer Hook

useReducer is an alternative to useState for complex state logic. It's similar to Redux patterns and is ideal when state transitions depend on the previous state.

import { useReducer } from 'react';

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

function TodoApp() {
  // 2. Initialize useReducer
  const [todos, dispatch] = useReducer(todoReducer, []);

  // 3. Dispatch actions
  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', payload: text });
  };

  return (
    <div>
      <input
        onKeyDown={(e) => {
          if (e.key === 'Enter') {
            addTodo(e.target.value);
            e.target.value = '';
          }
        }}
      />
      {todos.map(todo => (
        <div key={todo.id}>
          <span
            onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </span>
          <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
            ✕
          </button>
        </div>
      ))}
    </div>
  );
}

12. useContext Hook

useContext provides a way to pass data through the component tree without manually passing props at every level (solving prop drilling).

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

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

// 2. Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('dark');

  const toggleTheme = () => setTheme(prev =>
    prev === 'dark' ? 'light' : 'dark'
  );

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

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

  return (
    <button
      onClick={toggleTheme}
      className={`btn-${theme}`}
    >
      Current theme: {theme}
    </button>
  );
}

// 4. Wrap app with provider
function App() {
  return (
    <ThemeProvider>
      <Header />  {/* ThemedButton can be anywhere in the tree */}
    </ThemeProvider>
  );
}

Performance note: When a context value changes, all consumers re-render. For performance-critical scenarios, split contexts or use useMemo on the provider value.

13. Custom Hooks

Custom Hooks let you extract component logic into reusable functions. A custom Hook is simply a JavaScript function whose name starts with "use" and that may call other Hooks.

// Custom Hook: useLocalStorage
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

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

  useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Usage in any component
function UserProfile() {
  const [name, setName] = useLocalStorage('user-name', '');
  const { data: user, loading } = useFetch(`/api/user/${name}`);

  if (loading) return <Spinner />;
  return <div>{user?.name}</div>;
}

14. What is Prop Drilling?

Prop drilling occurs when you pass data through multiple layers of components that don't need the data, just to reach a deeply nested child that does.

// ❌ Prop drilling example: theme passes through 3 unnecessary layers
function App() {
  const [theme, setTheme] = useState('dark');
  return <Layout theme={theme} />;  // Layout doesn't use theme
}

function Layout({ theme }) {
  return <Sidebar theme={theme} />;  // Sidebar doesn't use theme
}

function Sidebar({ theme }) {
  return <ThemedButton theme={theme} />;  // This component needs theme
}

// ✅ Solution: Use Context
function App() {
  return (
    <ThemeProvider>
      <Layout />  {/* No theme prop needed! */}
    </ThemeProvider>
  );
}

function ThemedButton() {
  const { theme } = useContext(ThemeContext);  // Direct access
  return <button className={`btn-${theme}`}>Toggle</button>;
}

15. React Context vs Redux

Both manage global state, but they serve different use cases.

Feature React Context Redux
Purpose Lightweight global state Complex state management
Learning curve Low Medium-High
DevTools None built-in Excellent (Redux DevTools)
Performance Re-renders all consumers Selective subscriptions
Best for Theme, auth, locale Large apps with complex state
Middleware Not built-in Redux Thunk, Redux Saga

Modern recommendation: Start with React Context for simple global state. If you need middleware, time-travel debugging, or have highly frequent updates, consider Redux Toolkit (RTK).

16. What are Keys in React?

Keys help React identify which items in a list have changed, been added, or removed. They give elements a stable identity across renders.

// ✅ Correct: Using unique, stable IDs
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

// ❌ Wrong: Using index as key (causes bugs with reordering/deletion)
{todos.map((todo, index) => (
  <li key={index}>{todo.text}</li>  // BAD!
))}

// ❌ Wrong: Using Math.random() as key (creates new key every render)
{todos.map(todo => (
  <li key={Math.random()}>{todo.text}</li>  // VERY BAD!
))}

When is index as key acceptable?

  • The list is static and will not be reordered or filtered
  • The list is generated from a non-deterministic source (like user input)
  • Items don't have unique IDs

17. What are Fragments?

Fragments let you group multiple elements without adding extra DOM nodes. They solve the "multiple root elements" JSX limitation.

// ❌ Problem: Can't return two elements without a wrapper
function Header() {
  return (
    // <div> adds an unnecessary DOM node
    <div>
      <h1>Title</h1>
      <p>Subtitle</p>
    </div>
  );
}

// ✅ Solution 1: Short syntax
function Header() {
  return (
    <>
      <h1>Title</h1>
      <p>Subtitle</p>
    </>
  );
}

// ✅ Solution 2: Full syntax (needed when passing key prop in lists)
function Header() {
  return (
    <Fragment>
      <h1>Title</h1>
      <p>Subtitle</p>
    </Fragment>
  );
}

18. Controlled vs Uncontrolled Components

This distinction matters primarily for form elements and how React manages their state.

Feature Controlled Uncontrolled
State source React state (component) DOM itself (ref)
Data flow Two-way binding Read from DOM when needed
Validation On every keystroke On submit
Best for Most forms, real-time validation Simple forms, file inputs
// ✅ Controlled Component (recommended)
function ControlledForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        required
      />
      <button type="submit">Login</button>
    </form>
  );
}

// ⚡ Uncontrolled Component (use ref for quick reads)
function UncontrolledForm() {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      email: emailRef.current.value,
      password: passwordRef.current.value,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" ref={emailRef} defaultValue="" required />
      <input type="password" ref={passwordRef} defaultValue="" required />
      <button type="submit">Login</button>
    </form>
  );
}

19. What is Lifting State Up?

Lifting state up is the pattern of moving shared state to the nearest common ancestor of components that need it. Instead of syncing state between siblings, the parent holds the state and passes it down.

// Two sibling components sharing state
function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');

  // State lives in parent, shared with both children
  return (
    <div>
      <TemperatureInput
        scale="celsius"
        value={celsius}
        onChange={setCelsius}
      />
      <TemperatureInput
        scale="fahrenheit"
        value={toFahrenheit(celsius)}
        onChange={(f) => setCelsius(toCelsius(f))}
      />
      <BoilingVerdict celsius={parseFloat(celsius)} />
    </div>
  );
}

function TemperatureInput({ scale, value, onChange }) {
  return (
    <fieldset>
      <legend>Enter temperature in {scale}:</legend>
      <input
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    </fieldset>
  );
}

20. What are Error Boundaries?

Error Boundaries are React components that catch JavaScript errors in their child component tree, log those errors, and display a fallback UI instead of crashing the entire app.

import { Component } from 'react';

class ErrorBoundary extends 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 });
    // Log to error reporting service
    console.error('Error caught:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Try Again
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <Dashboard />
</ErrorBoundary>

Note: Error Boundaries do NOT catch errors in event handlers, async code, server-side rendering, or errors in the boundary itself. Use try-catch in event handlers instead.

21. React.memo and Memoization

React.memo is a higher-order component that wraps a functional component and prevents it from re-rendering if its props haven't changed (shallow comparison).

import { memo, useState } from 'react';

// This component will only re-render if its props change
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
  console.log('ExpensiveList rendered'); // Only logs when items change

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onSelect(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

// Custom comparison function for deeper control
const MemoizedComponent = memo(
  function MyComponent({ user, settings }) {
    return <div>{user.name} - {settings.theme}</div>;
  },
  (prevProps, nextProps) => {
    // Return true if props are equal (skip re-render)
    return prevProps.user.id === nextProps.user.id
      && prevProps.settings.theme === nextProps.settings.theme;
  }
);

When to use React.memo: When a component renders often with the same props, or when it has expensive rendering logic. Don't wrap every component — it adds overhead.

22. What is Code Splitting?

Code splitting breaks your app into smaller bundles that load on demand, reducing the initial page load time.

import { lazy, Suspense } from 'react';

// Lazy load components — only loaded when needed
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<Spinner />}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/analytics" element={<Analytics />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// Preload on hover for instant navigation
function NavLink({ to, children }) {
  const preload = () => {
    if (to === '/dashboard') import('./pages/Dashboard');
    else if (to === '/settings') import('./pages/Settings');
  };

  return (
    <Link to={to} onMouseEnter={preload}>
      {children}
    </Link>
  );
}

23. What are Higher-Order Components (HOC)?

A Higher-Order Component is a function that takes a component and returns a new enhanced component. HOCs are used for cross-cutting concerns like authentication, logging, or data fetching.

// HOC: Adds loading state to any component
function withLoading(WrappedComponent) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) return <div className="spinner">Loading...</div>;
    return <WrappedComponent {...props} />;
  };
}

// HOC: Adds authentication check
function withAuth(WrappedComponent) {
  return function WithAuthComponent(props) {
    const { user, loading } = useAuth();

    if (loading) return <Spinner />;
    if (!user) return <Navigate to="/login" />;

    return <WrappedComponent {...props} user={user} />;
  };
}

// Usage
const UserDashboard = withAuth(withLoading(Dashboard));

// Or compose multiple HOCs
const EnhancedComponent = compose(
  withAuth,
  withLoading,
  withLogger
)(Dashboard);

Modern alternative: Custom Hooks are now preferred over HOCs for most use cases. HOCs are still valid but can cause wrapper hell and make debugging harder.

24. What are React Portals?

Portals let you render children into a DOM node that exists outside the parent component's DOM hierarchy. Useful for modals, tooltips, and dropdowns that need to escape overflow constraints.

import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose}>✕</button>
        {children}
      </div>
    </div>,
    document.getElementById('modal-root') // Render outside #app
  );
}

// In index.html:
// <div id="root"></div>
// <div id="modal-root"></div>

25. Suspense and Lazy Loading

Suspense lets you show a fallback while waiting for something to load — lazy components, data, or images.

import { Suspense, lazy } from 'react';

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

function App() {
  return (
    <div>
      {/* Suspense boundary shows fallback while HeavyComponent loads */}
      <Suspense fallback={<div className="skeleton">Loading...</div>}>
        <HeavyComponent />
      </Suspense>

      {/* Nested Suspense boundaries for different sections */}
      <Suspense fallback={<Spinner />}>
        <Suspense fallback={<SidebarSkeleton />}>
          <Sidebar />
        </Suspense>
        <Suspense fallback={<ContentSkeleton />}>
          <MainContent />
        </Suspense>
      </Suspense>
    </div>
  );
}

26. What is the useEffect cleanup function?

The cleanup function returned from useEffect runs before the effect re-fires and when the component unmounts. It's essential for preventing memory leaks.

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    // Cleanup: runs when roomId changes or component unmounts
    return () => {
      connection.disconnect();
    };
  }, [roomId]);

  return <div>Welcome to room {roomId}!</div>;
}

// Timer example
function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    // Cleanup: clear interval on unmount
    return () => clearInterval(interval);
  }, []);

  return <p>Elapsed: {seconds}s</p>;
}

// Subscription example
function WindowSize() {
  const [size, setSize] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setSize(window.innerWidth);
    window.addEventListener('resize', handleResize);

    // Cleanup: remove event listener
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return <p>Window width: {size}px</p>;
}

27. What are Synthetic Events?

React's SyntheticEvent is a cross-browser wrapper around the browser's native event. It normalizes event handling so events work consistently across all browsers.

function ClickHandler() {
  // Synthetic Event
  const handleClick = (e) => {
    e.preventDefault();    // Works cross-browser
    e.stopPropagation();   // Works cross-browser

    console.log('Target:', e.target);           // Native DOM element
    console.log('Current Target:', e.currentTarget); // Element with handler
    console.log('Event Type:', e.type);         // 'click'

    // Access native event if needed
    console.log('Native event:', e.nativeEvent);
  };

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

// Passing event data
function ListItem({ item, onDelete }) {
  const handleDelete = (e, id) => {
    e.stopPropagation();
    onDelete(id);
  };

  return (
    <li>
      {item.name}
      <button onClick={(e) => handleDelete(e, item.id)}>Delete</button>
    </li>
  );
}

28. How does React handle forms?

React handles forms through controlled components where form data is handled by React state, or uncontrolled components where form data is handled by the DOM.

// Complete form example with validation
function RegistrationForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    password: '',
  });
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!formData.name.trim()) newErrors.name = 'Name is required';
    if (!formData.email.includes('@')) newErrors.email = 'Invalid email';
    if (formData.password.length < 8) newErrors.password = 'Min 8 characters';
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  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]: '' }));
  };

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

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input name="name" value={formData.name} onChange={handleChange} />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>
      <div>
        <input name="email" value={formData.email} onChange={handleChange} />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      <div>
        <input name="password" type="password" value={formData.password} onChange={handleChange} />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      <button type="submit">Register</button>
    </form>
  );
}

29. What is Server-Side Rendering (SSR)?

SSR renders React components on the server and sends fully rendered HTML to the client. Next.js is the most popular React framework for SSR.

Feature CSR (Client-Side) SSR (Server-Side) SSG (Static)
Rendering In browser On server per request At build time
SEO Poor (content not in HTML) Excellent Excellent
Performance Slow initial load Fast first paint Fastest
Best for SPAs, dashboards Dynamic content Blogs, docs, marketing

Next.js App Router uses React Server Components by default, which render on the server and send zero JavaScript to the client for those components — combining the best of SSR and SSG.

30. React性能优化技巧

Here are the top performance optimization techniques every React developer should know.

1. Avoid Unnecessary Re-renders

// ❌ Creating new objects/functions on every render
function Parent() {
  return (
    <Child
      style={{ color: 'red' }}       // new object every render!
      onClick={() => doSomething()} // new function every render!
    />
  );
}

// ✅ Memoize objects and functions
const style = { color: 'red' };
const handleClick = useCallback(() => doSomething(), []);

function Parent() {
  return <Child style={style} onClick={handleClick} />;
}

2. Use React.memo for Pure Components

// Only re-renders when props change
const ListItem = memo(function ListItem({ item, onDelete }) {
  return (
    <li>
      {item.name}
      <button onClick={() => onDelete(item.id)}>Delete</button>
    </li>
  );
});

3. Virtualize Long Lists

// Only render visible items (use react-window or react-virtualized)
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={500}
      width="100%"
      itemCount={items.length}
      itemSize={50}
    >
      {({ index, style }) => (
        <div style={style}>{items[index].name}</div>
      )}
    </FixedSizeList>
  );
}

4. Lazy Load Heavy Components

const Chart = lazy(() => import('./Chart'));
const DataTable = lazy(() => import('./DataTable'));

// Only load when needed
<Suspense fallback={<Spinner />}>
  {showChart && <Chart data={data} />}
</Suspense>

5. Profile and Measure

// Use React DevTools Profiler to identify bottlenecks
// Use Chrome Performance tab to measure actual render time
// Use Why Did You Render (wdyr) library to trace unnecessary renders

// Install: npm install @welldone-software/why-did-you-render
import React from 'react';
import wdyr from '@welldone-software/why-did-you-render';

if (process.env.NODE_ENV === 'development') {
  wdyr(React);
}

Quick Checklist

  • Use React.memo for components that receive stable props
  • Use useCallback for functions passed as props
  • Use useMemo for expensive computations
  • Avoid creating objects/arrays inline in JSX
  • Use lazy loading and code splitting
  • Virtualize long lists (100+ items)
  • Keep state as close to where it's used as possible
  • Use production build for performance testing

Final tip: Always measure before optimizing. Use console.time(), React DevTools Profiler, and Chrome Lighthouse to identify actual bottlenecks before applying optimizations.

Final Thoughts

React interviews test more than just knowledge — they assess your understanding of why things work the way they do. Focus on understanding the concepts behind these answers rather than memorizing them.

Key Takeaways

  • Master the fundamentals: JSX, components, props, state, and the Virtual DOM
  • Understand Hooks deeply — especially useState, useEffect, useRef, and useContext
  • Know when to use Context vs Redux vs local state
  • Practice writing custom Hooks and explaining their benefits
  • Be comfortable with performance optimization patterns
  • Understand server-side rendering concepts for modern React apps

Practice makes perfect: Build projects using these concepts. The best way to prepare for React interviews is to actually build React apps and explain your decisions along the way.

Back to Top