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
- Fundamental React Concepts
- React Hooks Deep Dive
- State Management Questions
- Performance Optimization
- Advanced React Patterns
- 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:
- Virtual DOM: Creates a virtual representation of the DOM for efficient updates
- Component-Based Architecture: Builds encapsulated components that manage their own state
- Declarative Programming: Describes what the UI should look like for any given state
- One-Way Data Binding: Data flows down from parent to child components
- JSX Syntax: JavaScript extension that allows writing HTML-like code
// 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:
Aspect | Functional Components | Class Components |
---|---|---|
Syntax | Simpler, more readable | More verbose with class syntax |
State | useState hook | this.state and setState |
Lifecycle | useEffect hook | componentDidMount, componentDidUpdate, etc. |
Performance | Better optimization with hooks | May have unnecessary re-renders |
Testing | Easier to test | More 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:
- Must return a single parent element (or use React.Fragment)
- All tags must be closed
- Use camelCase for attributes (className, onClick)
- Use curly braces {} for JavaScript expressions
// 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:
- Initial Render: React creates a Virtual DOM tree
- State Change: When state changes, React creates a new Virtual DOM tree
- Diffing: React compares the old and new Virtual DOM trees
- 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:
- Faster than direct DOM manipulation
- Batch updates for better performance
- Cross-platform compatibility
- Automatic optimization
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:
- useCallback: When passing callbacks to optimized child components that rely on reference equality
- useMemo: When you have expensive calculations that don’t need to run on every render
- Don’t overuse: Only use when there’s a measurable performance benefit
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
- Not handling errors properly in async operations
- Forgetting to add cleanup functions in useEffect
- Mutating state directly instead of using setState functions
- Not using keys properly in lists
- Overusing useCallback and useMemo without performance benefits
- Not understanding the dependency array in useEffect
Performance Best Practices Summary
Practice | Why It Matters | When to Use |
---|---|---|
React.memo | Prevents unnecessary re-renders | Components with expensive rendering |
useCallback | Memoizes function references | Callbacks passed to memoized children |
useMemo | Memoizes expensive calculations | Heavy computations or complex filtering |
Code Splitting | Reduces initial bundle size | Large applications with multiple routes |
Virtual Scrolling | Handles large datasets efficiently | Lists with thousands of items |
Final Preparation Tips
- Practice coding these examples by hand
- Understand the reasoning behind each pattern
- Be prepared to explain trade-offs and alternatives
- Know when NOT to use certain patterns
- 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!