Plaintext Engineering

ReactJS Common Interview Questions - Complete Guide 2025

Aug 28, 2025 • 17 min read


ReactJS continues to be one of the most in-demand skills for frontend developers in 2025. Whether you’re a junior developer preparing for your first React interview or a senior engineer looking to refresh your knowledge, mastering these common interview questions is essential for success. This comprehensive guide covers the most frequently asked ReactJS interview questions, organized by difficulty level and topic area.

Table of Contents

  1. Fundamental React Concepts
  2. React Hooks Deep Dive
  3. State Management Questions
  4. Performance Optimization
  5. Advanced React Patterns
  6. Practical Coding Questions

Fundamental React Concepts

1. What is React and what are its key features?

React is a JavaScript library developed by Facebook for building user interfaces, particularly single-page applications. Its key features include:

// Example of a simple React component
import React from 'react';

const Welcome = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

export default Welcome;

2. Explain the difference between functional and class components

Functional Components (Recommended since React 16.8):

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

const FunctionalComponent = ({ title }) => {
  const [count, setCount] = useState(0);

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

  return (
    <div>
      <h2>{title}</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

Class Components:

import React, { Component } from 'react';

class ClassComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }

  componentDidUpdate() {
    document.title = `Count: ${this.state.count}`;
  }

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

  render() {
    return (
      <div>
        <h2>{this.props.title}</h2>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

Key Differences:

AspectFunctional ComponentsClass Components
SyntaxSimpler, more readableMore verbose with class syntax
StateuseState hookthis.state and setState
LifecycleuseEffect hookcomponentDidMount, componentDidUpdate, etc.
PerformanceBetter optimization with hooksMay have unnecessary re-renders
TestingEasier to testMore complex testing setup

3. What is JSX and how does it work?

JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code in your JavaScript files. It gets transformed into regular JavaScript function calls.

// JSX syntax
const element = (
  <div className="container">
    <h1>Hello World</h1>
    <p>This is JSX</p>
  </div>
);

// Gets transformed to:
const element = React.createElement(
  'div',
  { className: 'container' },
  React.createElement('h1', null, 'Hello World'),
  React.createElement('p', null, 'This is JSX')
);

JSX Rules:

// Using React.Fragment to avoid extra div
const Component = () => (
  <>
    <h1>Title</h1>
    <p>Content</p>
  </>
);

// JavaScript expressions in JSX
const name = 'John';
const element = <h1>Hello, {name}!</h1>;

// Conditional rendering
const isLoggedIn = true;
const greeting = isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in.</h1>;

4. Explain React’s Virtual DOM and how it works

The Virtual DOM is a lightweight copy of the actual DOM that React uses to optimize rendering performance.

How it works:

  1. Initial Render: React creates a Virtual DOM tree
  2. State Change: When state changes, React creates a new Virtual DOM tree
  3. Diffing: React compares the old and new Virtual DOM trees
  4. Reconciliation: Only the differences are applied to the actual DOM
// Example showing Virtual DOM optimization
const OptimizedList = ({ items }) => {
  const [filter, setFilter] = useState('');

  const filteredItems = useMemo(() => {
    return items.filter((item) => item.name.toLowerCase().includes(filter.toLowerCase()));
  }, [items, filter]);

  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter items..."
      />
      <ul>
        {filteredItems.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

Benefits:

React Hooks Deep Dive

5. What are React Hooks and how do they work?

Hooks are functions that allow you to “hook into” React state and lifecycle features from function components. They were introduced in React 16.8.

Basic Hooks:

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

const HooksExample = () => {
  // useState - State management
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: '', email: '' });

  // useEffect - Side effects
  useEffect(() => {
    document.title = `Count: ${count}`;

    // Cleanup function
    return () => {
      document.title = 'React App';
    };
  }, [count]);

  // Custom hook
  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;
  };

  const windowSize = useWindowSize();

  return (
    <div>
      <h2>Hooks Example</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>
        Window size: {windowSize.width} x {windowSize.height}
      </p>
    </div>
  );
};

6. Explain useState and useEffect in detail

useState Hook:

const StateExample = () => {
  // Basic state
  const [count, setCount] = useState(0);

  // Object state
  const [user, setUser] = useState({
    name: 'John',
    age: 30,
    email: 'john@example.com',
  });

  // Array state
  const [items, setItems] = useState([]);

  // Functional update
  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };

  // Update object state
  const updateUser = (field, value) => {
    setUser((prevUser) => ({
      ...prevUser,
      [field]: value,
    }));
  };

  // Update array state
  const addItem = (item) => {
    setItems((prevItems) => [...prevItems, item]);
  };

  const removeItem = (id) => {
    setItems((prevItems) => prevItems.filter((item) => item.id !== id));
  };

  return (
    <div>
      <h2>State Management</h2>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>

      <div>
        <input
          value={user.name}
          onChange={(e) => updateUser('name', e.target.value)}
          placeholder="Name"
        />
        <input
          value={user.age}
          onChange={(e) => updateUser('age', parseInt(e.target.value))}
          placeholder="Age"
          type="number"
        />
      </div>

      <p>
        User: {user.name}, Age: {user.age}
      </p>
    </div>
  );
};

useEffect Hook:

const EffectExample = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // Effect with no dependencies (runs only once)
  useEffect(() => {
    console.log('Component mounted');

    return () => {
      console.log('Component will unmount');
    };
  }, []);

  // Effect with dependencies
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch('/api/data');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []); // Empty dependency array means run once

  // Effect that runs when data changes
  useEffect(() => {
    if (data) {
      localStorage.setItem('userData', JSON.stringify(data));
    }
  }, [data]);

  // Cleanup effect
  useEffect(() => {
    const interval = setInterval(() => {
      console.log('Timer tick');
    }, 1000);

    return () => clearInterval(interval);
  }, []);

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

  return (
    <div>
      <h2>Data: {JSON.stringify(data)}</h2>
    </div>
  );
};

7. What are useCallback and useMemo, and when should you use them?

useCallback - Memoizes functions:

const CallbackExample = ({ userId }) => {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState(null);

  // Without useCallback - function recreated on every render
  const fetchUser = async () => {
    const response = await fetch(`/api/users/${userId}`);
    const userData = await response.json();
    setUser(userData);
  };

  // With useCallback - function memoized, only recreates when userId changes
  const fetchUserMemoized = useCallback(async () => {
    const response = await fetch(`/api/users/${userId}`);
    const userData = await response.json();
    setUser(userData);
  }, [userId]);

  // Memoized callback for event handlers
  const handleClick = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return (
    <div>
      <button onClick={handleClick}>Count: {count}</button>
      <button onClick={fetchUserMemoized}>Fetch User</button>
      {user && <p>User: {user.name}</p>}
    </div>
  );
};

useMemo - Memoizes values:

const MemoExample = ({ items, filterText }) => {
  const [count, setCount] = useState(0);

  // Without useMemo - expensive calculation runs on every render
  const expensiveFilter = items.filter((item) =>
    item.name.toLowerCase().includes(filterText.toLowerCase())
  );

  // With useMemo - calculation only runs when items or filterText change
  const memoizedFilter = useMemo(() => {
    console.log('Filtering items...'); // Only logs when dependencies change
    return items.filter((item) => item.name.toLowerCase().includes(filterText.toLowerCase()));
  }, [items, filterText]);

  // Memoized expensive calculation
  const expensiveCalculation = useMemo(() => {
    console.log('Performing expensive calculation...');
    return items.reduce((sum, item) => sum + item.value, 0) * Math.PI;
  }, [items]);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Re-render: {count}</button>
      <p>Filtered items: {memoizedFilter.length}</p>
      <p>Expensive result: {expensiveCalculation.toFixed(2)}</p>
    </div>
  );
};

When to use them:

State Management Questions

8. How do you manage state in React applications?

Local State (useState):

const LocalStateExample = () => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });

  const handleChange = (field, value) => {
    setFormData((prev) => ({
      ...prev,
      [field]: value,
    }));
  };

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

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={formData.name}
        onChange={(e) => handleChange('name', e.target.value)}
        placeholder="Name"
      />
      <input
        type="email"
        value={formData.email}
        onChange={(e) => handleChange('email', e.target.value)}
        placeholder="Email"
      />
      <textarea
        value={formData.message}
        onChange={(e) => handleChange('message', e.target.value)}
        placeholder="Message"
      />
      <button type="submit">Submit</button>
    </form>
  );
};

Context API for Global State:

// Create context
const UserContext = React.createContext();

// Provider component
const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);

  const login = async (credentials) => {
    setLoading(true);
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials),
      });
      const userData = await response.json();
      setUser(userData);
    } catch (error) {
      console.error('Login failed:', error);
    } finally {
      setLoading(false);
    }
  };

  const logout = () => {
    setUser(null);
  };

  const value = {
    user,
    loading,
    login,
    logout,
  };

  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};

// Custom hook to use context
const useUser = () => {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
};

// Component using context
const UserProfile = () => {
  const { user, logout } = useUser();

  if (!user) {
    return <div>Please log in</div>;
  }

  return (
    <div>
      <h2>Welcome, {user.name}!</h2>
      <p>Email: {user.email}</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
};

useReducer for Complex State:

const initialState = {
  count: 0,
  history: [],
  loading: false,
  error: null,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
        history: [...state.history, state.count],
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
        history: [...state.history, state.count],
      };
    case 'RESET':
      return {
        ...state,
        count: 0,
        history: [],
      };
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    case 'SET_ERROR':
      return { ...state, error: action.payload };
    default:
      return state;
  }
};

const ReducerExample = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const increment = () => dispatch({ type: 'INCREMENT' });
  const decrement = () => dispatch({ type: 'DECREMENT' });
  const reset = () => dispatch({ type: 'RESET' });

  return (
    <div>
      <h2>Count: {state.count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>

      <h3>History:</h3>
      <ul>
        {state.history.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
    </div>
  );
};

Performance Optimization

9. How do you optimize React application performance?

React.memo for Component Memoization:

// Without memoization - re-renders on every parent render
const ExpensiveComponent = ({ data, onUpdate }) => {
  console.log('ExpensiveComponent rendering');

  // Expensive operation
  const processedData = data.map((item) => ({
    ...item,
    processed: item.value * Math.PI,
  }));

  return (
    <div>
      <h3>Processed Data</h3>
      <ul>
        {processedData.map((item) => (
          <li key={item.id}>
            {item.name}: {item.processed.toFixed(2)}
          </li>
        ))}
      </ul>
      <button onClick={onUpdate}>Update</button>
    </div>
  );
};

// With memoization - only re-renders when props change
const MemoizedExpensiveComponent = React.memo(ExpensiveComponent);

// Parent component
const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [data, setData] = useState([
    { id: 1, name: 'Item 1', value: 10 },
    { id: 2, name: 'Item 2', value: 20 },
  ]);

  const handleUpdate = useCallback(() => {
    setData((prev) =>
      prev.map((item) => ({
        ...item,
        value: item.value + 1,
      }))
    );
  }, []);

  return (
    <div>
      <h2>Parent Component</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>

      <MemoizedExpensiveComponent data={data} onUpdate={handleUpdate} />
    </div>
  );
};

Code Splitting with React.lazy:

import React, { Suspense, lazy } from 'react';

// Lazy load components
const LazyComponent = lazy(() => import('./LazyComponent'));
const AnotherLazyComponent = lazy(() => import('./AnotherLazyComponent'));

const CodeSplittingExample = () => {
  const [showLazy, setShowLazy] = useState(false);
  const [showAnother, setShowAnother] = useState(false);

  return (
    <div>
      <h2>Code Splitting Example</h2>

      <button onClick={() => setShowLazy(!showLazy)}>
        {showLazy ? 'Hide' : 'Show'} Lazy Component
      </button>

      <button onClick={() => setShowAnother(!showAnother)}>
        {showAnother ? 'Hide' : 'Show'} Another Lazy Component
      </button>

      <Suspense fallback={<div>Loading...</div>}>
        {showLazy && <LazyComponent />}
        {showAnother && <AnotherLazyComponent />}
      </Suspense>
    </div>
  );
};

Virtual Scrolling for Large Lists:

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

const VirtualScrollingExample = () => {
  const items = Array.from({ length: 10000 }, (_, index) => ({
    id: index,
    name: `Item ${index}`,
    description: `This is item number ${index}`,
  }));

  const Row = ({ index, style }) => (
    <div style={style} className="row">
      <strong>{items[index].name}</strong>
      <span>{items[index].description}</span>
    </div>
  );

  return (
    <div>
      <h2>Virtual Scrolling Example</h2>
      <List height={400} itemCount={items.length} itemSize={50} width="100%">
        {Row}
      </List>
    </div>
  );
};

Advanced React Patterns

10. What are Render Props and Higher-Order Components?

Render Props Pattern:

// Render prop component
const DataFetcher = ({ url, render }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return render({ data, loading, error });
};

// Using render props
const UserList = () => (
  <DataFetcher
    url="/api/users"
    render={({ data, loading, error }) => {
      if (loading) return <div>Loading...</div>;
      if (error) return <div>Error: {error}</div>;

      return (
        <ul>
          {data.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
    }}
  />
);

const UserStats = () => (
  <DataFetcher
    url="/api/users"
    render={({ data, loading, error }) => {
      if (loading) return <div>Loading stats...</div>;
      if (error) return <div>Error loading stats</div>;

      const totalUsers = data.length;
      const activeUsers = data.filter((user) => user.active).length;

      return (
        <div>
          <p>Total Users: {totalUsers}</p>
          <p>Active Users: {activeUsers}</p>
        </div>
      );
    }}
  />
);

Higher-Order Component (HOC):

// HOC for authentication
const withAuth = (WrappedComponent) => {
  return function AuthenticatedComponent(props) {
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      // Check authentication status
      const checkAuth = async () => {
        try {
          const token = localStorage.getItem('token');
          if (token) {
            const response = await fetch('/api/verify', {
              headers: { Authorization: `Bearer ${token}` },
            });
            if (response.ok) {
              const userData = await response.json();
              setUser(userData);
              setIsAuthenticated(true);
            }
          }
        } catch (error) {
          console.error('Auth check failed:', error);
        } finally {
          setLoading(false);
        }
      };

      checkAuth();
    }, []);

    if (loading) {
      return <div>Checking authentication...</div>;
    }

    if (!isAuthenticated) {
      return <div>Please log in to access this page</div>;
    }

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

// HOC for error boundaries
const withErrorBoundary = (WrappedComponent) => {
  return class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false, error: null };
    }

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

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

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

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

// Using HOCs
const ProtectedUserProfile = withAuth(withErrorBoundary(UserProfile));

Practical Coding Questions

11. Implement a custom hook for form management

const useForm = (initialValues, validationSchema) => {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (field, value) => {
    setValues((prev) => ({ ...prev, [field]: value }));

    // Clear error when user starts typing
    if (errors[field]) {
      setErrors((prev) => ({ ...prev, [field]: '' }));
    }
  };

  const handleBlur = (field) => {
    setTouched((prev) => ({ ...prev, [field]: true }));

    // Validate field on blur
    if (validationSchema && validationSchema[field]) {
      const fieldError = validationSchema[field](values[field]);
      setErrors((prev) => ({ ...prev, [field]: fieldError }));
    }
  };

  const validateForm = () => {
    if (!validationSchema) return {};

    const newErrors = {};
    Object.keys(validationSchema).forEach((field) => {
      const error = validationSchema[field](values[field]);
      if (error) {
        newErrors[field] = error;
      }
    });

    setErrors(newErrors);
    return newErrors;
  };

  const handleSubmit = async (onSubmit) => {
    const formErrors = validateForm();

    if (Object.keys(formErrors).length === 0) {
      setIsSubmitting(true);
      try {
        await onSubmit(values);
      } catch (error) {
        console.error('Form submission failed:', error);
      } finally {
        setIsSubmitting(false);
      }
    }
  };

  const resetForm = () => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
    setIsSubmitting(false);
  };

  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    resetForm,
  };
};

// Usage example
const validationSchema = {
  name: (value) => (!value ? 'Name is required' : ''),
  email: (value) => {
    if (!value) return 'Email is required';
    if (!/\S+@\S+\.\S+/.test(value)) return 'Email is invalid';
    return '';
  },
  password: (value) => {
    if (!value) return 'Password is required';
    if (value.length < 6) return 'Password must be at least 6 characters';
    return '';
  },
};

const FormExample = () => {
  const form = useForm({ name: '', email: '', password: '' }, validationSchema);

  const onSubmit = async (values) => {
    console.log('Form submitted:', values);
    // API call here
  };

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit(onSubmit)();
      }}
    >
      <div>
        <input
          type="text"
          placeholder="Name"
          value={form.values.name}
          onChange={(e) => form.handleChange('name', e.target.value)}
          onBlur={() => form.handleBlur('name')}
        />
        {form.touched.name && form.errors.name && <span className="error">{form.errors.name}</span>}
      </div>

      <div>
        <input
          type="email"
          placeholder="Email"
          value={form.values.email}
          onChange={(e) => form.handleChange('email', e.target.value)}
          onBlur={() => form.handleBlur('email')}
        />
        {form.touched.email && form.errors.email && (
          <span className="error">{form.errors.email}</span>
        )}
      </div>

      <div>
        <input
          type="password"
          placeholder="Password"
          value={form.values.password}
          onChange={(e) => form.handleChange('password', e.target.value)}
          onBlur={() => form.handleBlur('password')}
        />
        {form.touched.password && form.errors.password && (
          <span className="error">{form.errors.password}</span>
        )}
      </div>

      <button type="submit" disabled={form.isSubmitting}>
        {form.isSubmitting ? 'Submitting...' : 'Submit'}
      </button>

      <button type="button" onClick={form.resetForm}>
        Reset
      </button>
    </form>
  );
};

12. Create a reusable Modal component with custom hook

// Custom hook for modal management
const useModal = () => {
  const [isOpen, setIsOpen] = useState(false);

  const openModal = () => setIsOpen(true);
  const closeModal = () => setIsOpen(false);
  const toggleModal = () => setIsOpen(!isOpen);

  return {
    isOpen,
    openModal,
    closeModal,
    toggleModal,
  };
};

// Modal component
const Modal = ({ isOpen, onClose, title, children, size = 'medium' }) => {
  const modalRef = useRef();

  useEffect(() => {
    const handleEscape = (e) => {
      if (e.key === 'Escape') {
        onClose();
      }
    };

    if (isOpen) {
      document.addEventListener('keydown', handleEscape);
      document.body.style.overflow = 'hidden';
    }

    return () => {
      document.removeEventListener('keydown', handleEscape);
      document.body.style.overflow = 'unset';
    };
  }, [isOpen, onClose]);

  const handleBackdropClick = (e) => {
    if (e.target === modalRef.current) {
      onClose();
    }
  };

  if (!isOpen) return null;

  const sizeClasses = {
    small: 'max-w-md',
    medium: 'max-w-lg',
    large: 'max-w-2xl',
    xlarge: 'max-w-4xl',
  };

  return (
    <div
      ref={modalRef}
      className="bg-opacity-50 fixed inset-0 z-50 flex items-center justify-center bg-black"
      onClick={handleBackdropClick}
    >
      <div className={`rounded-lg bg-white shadow-xl ${sizeClasses[size]} mx-4 w-full`}>
        <div className="flex items-center justify-between border-b p-4">
          <h2 className="text-xl font-semibold">{title}</h2>
          <button onClick={onClose} className="text-2xl text-gray-400 hover:text-gray-600">
            ×
          </button>
        </div>

        <div className="p-4">{children}</div>
      </div>
    </div>
  );
};

// Usage example
const ModalExample = () => {
  const infoModal = useModal();
  const formModal = useModal();
  const [formData, setFormData] = useState({ name: '', email: '' });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
    formModal.closeModal();
    setFormData({ name: '', email: '' });
  };

  return (
    <div>
      <h2>Modal Examples</h2>

      <button onClick={infoModal.openModal} className="btn-primary">
        Show Info Modal
      </button>

      <button onClick={formModal.openModal} className="btn-secondary">
        Show Form Modal
      </button>

      {/* Info Modal */}
      <Modal
        isOpen={infoModal.isOpen}
        onClose={infoModal.closeModal}
        title="Information"
        size="small"
      >
        <p>This is an informational modal with some content.</p>
        <div className="modal-actions">
          <button onClick={infoModal.closeModal} className="btn-primary">
            Close
          </button>
        </div>
      </Modal>

      {/* Form Modal */}
      <Modal
        isOpen={formModal.isOpen}
        onClose={formModal.closeModal}
        title="User Information"
        size="medium"
      >
        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label htmlFor="name">Name:</label>
            <input
              id="name"
              type="text"
              value={formData.name}
              onChange={(e) =>
                setFormData((prev) => ({
                  ...prev,
                  name: e.target.value,
                }))
              }
              required
            />
          </div>

          <div className="form-group">
            <label htmlFor="email">Email:</label>
            <input
              id="email"
              type="email"
              value={formData.email}
              onChange={(e) =>
                setFormData((prev) => ({
                  ...prev,
                  email: e.target.value,
                }))
              }
              required
            />
          </div>

          <div className="modal-actions">
            <button type="button" onClick={formModal.closeModal} className="btn-secondary">
              Cancel
            </button>
            <button type="submit" className="btn-primary">
              Save
            </button>
          </div>
        </form>
      </Modal>
    </div>
  );
};

export { Modal, useModal, ModalExample };

Key Interview Tips and Best Practices

Common Mistakes to Avoid

  1. Not handling errors properly in async operations
  2. Forgetting to add cleanup functions in useEffect
  3. Mutating state directly instead of using setState functions
  4. Not using keys properly in lists
  5. Overusing useCallback and useMemo without performance benefits
  6. Not understanding the dependency array in useEffect

Performance Best Practices Summary

PracticeWhy It MattersWhen to Use
React.memoPrevents unnecessary re-rendersComponents with expensive rendering
useCallbackMemoizes function referencesCallbacks passed to memoized children
useMemoMemoizes expensive calculationsHeavy computations or complex filtering
Code SplittingReduces initial bundle sizeLarge applications with multiple routes
Virtual ScrollingHandles large datasets efficientlyLists with thousands of items

Final Preparation Tips

  1. Practice coding these examples by hand
  2. Understand the reasoning behind each pattern
  3. Be prepared to explain trade-offs and alternatives
  4. Know when NOT to use certain patterns
  5. Stay updated with latest React features and best practices

React interviews often focus on practical problem-solving rather than just theoretical knowledge. The key is to demonstrate not just what you know, but how you apply that knowledge to build maintainable, performant applications. Good luck with your React interviews!

Sources

Related articles