100 React Interview Questions and Answers for Developers
Interview preparation · Technical guide
React Fundamentals: Interview Questions and Answers
React components, state, rendering, application architecture, and testing. React 19 changes and legacy tooling are distinguished from older examples.
Examples are independent teaching snippets and may require application types, imports, packages, schema, and configuration. Framework behavior is version-dependent. Corrections address identified issues; the complete source code collection has not been compiled or integration-tested.
1. Explain the differences between React and Angular
React: - Library vs Framework: React is a JavaScript library for building user interfaces, while Angular is a full-featured framework - Learning Curve: React has a gentler learning curve with fewer concepts to master - Flexibility: React offers more flexibility in choosing libraries and tools - Bundle Size: Generally smaller bundle size due to being a library - Community: Larger ecosystem with more third-party libraries
Angular: - Complete Framework: Provides everything needed for enterprise applications - TypeScript First: Built with TypeScript from the ground up - Opinionated: Enforces specific patterns and conventions - Built-in Features: Includes routing, forms, HTTP client, testing utilities - Enterprise Focus: Better suited for large-scale applications
Code Example - React vs Angular:
// React Component
import React, { useState } from 'react';
const UserProfile = ({ user }) => {
const [isEditing, setIsEditing] = useState(false);
return (
<div>
<h2>{user.name}</h2>
<button onClick={() => setIsEditing(!isEditing)}>
{isEditing ? 'Cancel' : 'Edit'}
</button>
</div>
);
};
// Angular Component
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-profile',
template: `
<div>
<h2>{{ user.name }}</h2>
<button (click)="toggleEditing()">
{{ isEditing ? 'Cancel' : 'Edit' }}
</button>
</div>
`
})
export class UserProfileComponent {
@Input() user: any;
isEditing = false;
toggleEditing() {
this.isEditing = !this.isEditing;
}
}
2. What is the difference between React and React Native?
React: - Web Platform: Designed for web browsers - DOM Manipulation: Uses virtual DOM for web rendering - Styling: CSS, CSS-in-JS, or styled-components - Navigation: React Router for web navigation - Platform APIs: Limited to web APIs
React Native: - Mobile Platform: Designed for iOS and Android - Native Components: Renders to native UI components - Styling: Flexbox-based styling system - Navigation: React Navigation for mobile navigation - Platform APIs: Access to native device features
Code Example:
// React Web Component
import React from 'react';
import './Button.css';
const Button = ({ onPress, title }) => (
<button className="custom-button" onClick={onPress}>
{title}
</button>
);
// React Native Component
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
const Button = ({ onPress, title }) => (
<TouchableOpacity style={styles.button} onPress={onPress}>
<Text style={styles.text}>{title}</Text>
</TouchableOpacity>
);
const styles = StyleSheet.create({
button: {
backgroundColor: '#007AFF',
padding: 12,
borderRadius: 8,
},
text: {
color: 'white',
textAlign: 'center',
},
});
3. Explain React's component-based architecture
React's component-based architecture is built on the principle of breaking down complex UIs into smaller, reusable pieces called components.
Key Principles: - Composition: Components can be composed together - Reusability: Components can be reused across the application - Encapsulation: Each component manages its own logic and state - Single Responsibility: Each component has a specific purpose
Code Example:
// Atomic Design Pattern Example
const Button = ({ children, variant = 'primary', ...props }) => (
<button className={`btn btn-${variant}`} {...props}>
{children}
</button>
);
const Card = ({ title, children, actions }) => (
<div className="card">
<div className="card-header">
<h3>{title}</h3>
</div>
<div className="card-body">{children}</div>
{actions && <div className="card-actions">{actions}</div>}
</div>
);
const UserProfile = ({ user }) => (
<Card
title="User Profile"
actions={
<Button variant="secondary">Edit Profile</Button>
}
>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
</Card>
);
4. What are the differences between functional and class components?
Functional Components: - Hooks: Use React hooks for state and lifecycle - Performance: Generally better performance due to less overhead - Modern: Recommended approach in modern React - Simpler: Less boilerplate code
Class Components:
- Lifecycle Methods: Use lifecycle methods for side effects
- Legacy: Older approach, still supported
- More Complex: Require more boilerplate code
- this Context: Need to handle this binding
Code Example:
// Functional Component with Hooks
import React, { useState, useEffect } from 'react';
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('Error fetching user:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
};
// Class Component
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true
};
}
async componentDidMount() {
await this.fetchUser();
}
async componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
await this.fetchUser();
}
}
fetchUser = async () => {
try {
const response = await fetch(`/api/users/${this.props.userId}`);
const userData = await response.json();
this.setState({ user: userData, loading: false });
} catch (error) {
console.error('Error fetching user:', error);
this.setState({ loading: false });
}
};
render() {
const { user, loading } = this.state;
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
}
5. Explain React's virtual DOM and its benefits
The Virtual DOM is a lightweight copy of the actual DOM that React uses to optimize rendering performance.
How it works: 1. State Change: When component state changes 2. Virtual DOM Update: React updates the virtual DOM 3. Diffing: React compares virtual DOM with previous version 4. Minimal Updates: Only necessary changes are applied to real DOM
Benefits: - Performance: Reduces expensive DOM operations - Cross-platform: Works in non-browser environments - Batching: Multiple updates are batched together - Optimization: React optimizes the update process
Code Example:
// Example showing Virtual DOM optimization
const UserList = ({ users }) => {
const [selectedUser, setSelectedUser] = useState(null);
return (
<div>
{users.map(user => (
<UserCard
key={user.id} // Helps React identify which items changed
user={user}
isSelected={selectedUser?.id === user.id}
onSelect={() => setSelectedUser(user)}
/>
))}
</div>
);
};
// Without key prop, React would re-render all items
// With key prop, React only updates the changed item
6. What is the difference between virtual DOM and real DOM?
Real DOM: - Browser API: Native browser representation - Expensive Operations: Direct manipulation is slow - Synchronous: Updates block the main thread - Memory Heavy: Each element has many properties
Virtual DOM: - JavaScript Object: Lightweight JavaScript representation - Fast Operations: JavaScript object manipulation is fast - Asynchronous: Updates can be batched and optimized - Memory Efficient: Only contains necessary information
Code Example:
// Real DOM manipulation (inefficient)
const updateUserList = (users) => {
const container = document.getElementById('user-list');
container.innerHTML = ''; // Expensive operation
users.forEach(user => {
const div = document.createElement('div');
div.textContent = user.name;
container.appendChild(div); // Expensive operation
});
};
// Virtual DOM (React handles this efficiently)
const UserList = ({ users }) => (
<div id="user-list">
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
7. Explain React's one-way data flow
React follows a unidirectional data flow where data flows down from parent to child components through props.
Key Principles: - Top-down: Data flows from parent to child - Props are Read-only: Child components cannot modify props - State Updates: Changes flow back up through callbacks - Predictable: Makes data flow easier to understand and debug
Code Example:
// Parent Component (Data Owner)
const UserDashboard = () => {
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState(null);
const handleUserSelect = (user) => {
setSelectedUser(user);
};
const handleUserUpdate = (updatedUser) => {
setUsers(prevUsers =>
prevUsers.map(user =>
user.id === updatedUser.id ? updatedUser : user
)
);
};
return (
<div>
<UserList
users={users}
onUserSelect={handleUserSelect}
selectedUser={selectedUser}
/>
{selectedUser && (
<UserDetail
user={selectedUser}
onUserUpdate={handleUserUpdate}
/>
)}
</div>
);
};
// Child Component (Data Consumer)
const UserList = ({ users, onUserSelect, selectedUser }) => (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
isSelected={selectedUser?.id === user.id}
onSelect={() => onUserSelect(user)}
/>
))}
</div>
);
8. What are the differences between props and state?
Props: - Read-only: Cannot be modified by the component - External: Passed from parent component - Immutable: Should not be changed within component - Configuration: Used for component configuration
State: - Mutable: Can be modified by the component - Internal: Managed within the component - Reactive: Changes trigger re-renders - Local Data: Used for component-specific data
Code Example:
const UserProfile = ({ userId, theme }) => { // props
const [user, setUser] = useState(null); // state
const [isEditing, setIsEditing] = useState(false); // state
const handleEdit = () => {
setIsEditing(true); // Modifying state
};
const handleSave = async (userData) => {
// Cannot modify props directly
// userId = 'new-id'; // This would cause an error
await updateUser(userId, userData); // Using props
setUser(userData); // Modifying state
setIsEditing(false);
};
return (
<div className={`profile ${theme}`}> {/* Using props */}
{isEditing ? ( // Using state
<EditForm user={user} onSave={handleSave} />
) : (
<UserInfo user={user} onEdit={handleEdit} />
)}
</div>
);
};
9. Explain React's reconciliation algorithm
Reconciliation is React's algorithm for determining what has changed in the component tree and updating the DOM accordingly.
Process: 1. Element Comparison: Compare new and old element types 2. Key-based Reconciliation: Use keys to identify which items changed 3. Diffing Algorithm: Find minimal number of changes needed 4. Batch Updates: Apply all changes efficiently
Code Example:
// Efficient reconciliation with keys
const TodoList = ({ todos }) => (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id} // Essential for efficient reconciliation
todo={todo}
/>
))}
</ul>
);
// Without keys, React would re-render all items
// With keys, React only updates changed items
10. What is the difference between reconciliation and rendering?
Reconciliation: - Process: Determining what needs to change - Virtual DOM: Works with virtual DOM - Algorithm: Diffing and comparison logic - Optimization: Minimizes actual DOM operations
Rendering: - Process: Actually updating the DOM - Real DOM: Works with actual DOM - Output: Visual changes on screen - Performance: Expensive browser operations
Code Example:
const UserList = ({ users }) => {
// Reconciliation happens here - React determines what changed
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
};
// Rendering happens in the browser - actual DOM updates
// React optimizes this by batching updates and minimizing DOM operations
Components & JSX
11. Explain React components and their types
React components are the building blocks of React applications, representing reusable UI pieces.
Types of Components:
- Functional Components (Recommended)
- Class Components (Legacy)
- Higher-Order Components (HOCs)
- Render Props Components
- Compound Components
Code Example:
// 1. Functional Component
const Button = ({ children, onClick }) => (
<button onClick={onClick}>{children}</button>
);
// 2. Class Component
class UserProfile extends React.Component {
render() {
return <div>{this.props.user.name}</div>;
}
}
// 3. Higher-Order Component
const withLoading = (WrappedComponent) => {
return function WithLoadingComponent(props) {
if (props.loading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
};
const UserListWithLoading = withLoading(UserList);
// 4. Render Props Component
const DataFetcher = ({ url, children }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return children({ data, loading });
};
// 5. Compound Components
const Select = ({ children, value, onChange }) => (
<div className="select">
{React.Children.map(children, child =>
React.cloneElement(child, { value, onChange })
)}
</div>
);
Select.Option = ({ children, value, onChange, selectedValue }) => (
<div
className={`option ${selectedValue === value ? 'selected' : ''}`}
onClick={() => onChange(value)}
>
{children}
</div>
);
12. What are the differences between controlled and uncontrolled components?
Controlled Components: - State Management: React controls the component state - Value Prop: Component value is controlled by React state - onChange Handler: React handles all changes - Predictable: State is always in sync with component
Uncontrolled Components: - DOM Management: DOM manages its own state - Ref Usage: Use refs to access DOM values - Imperative: Direct DOM manipulation - Performance: Slightly better performance for simple forms
Code Example:
// Controlled Component
const ControlledForm = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log({ name, email });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
};
// Uncontrolled Component
const UncontrolledForm = () => {
const nameRef = useRef();
const emailRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log({
name: nameRef.current.value,
email: emailRef.current.value
});
};
return (
<form onSubmit={handleSubmit}>
<input
ref={nameRef}
type="text"
placeholder="Name"
/>
<input
ref={emailRef}
type="email"
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
};
13. Explain JSX syntax and its transformation
JSX is a syntax extension for JavaScript that allows you to write HTML-like code in JavaScript.
JSX Features: - HTML-like Syntax: Familiar markup syntax - JavaScript Expressions: Embed JavaScript in JSX - Attributes: Use camelCase for HTML attributes - Children: Support for nested elements and expressions
Transformation:
JSX is transformed into React.createElement() calls by Babel.
Code Example:
// JSX Syntax
const UserCard = ({ user, onEdit }) => (
<div className="user-card">
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={onEdit}>
Edit {user.name}
</button>
</div>
);
// Transformed to:
const UserCard = ({ user, onEdit }) =>
React.createElement(
'div',
{ className: 'user-card' },
React.createElement('h2', null, user.name),
React.createElement('p', null, user.email),
React.createElement(
'button',
{ onClick: onEdit },
'Edit ',
user.name
)
);
14. What is the difference between JSX and HTML?
JSX:
- JavaScript Extension: Part of JavaScript
- Dynamic Content: Can embed JavaScript expressions
- CamelCase Attributes: className, onClick, etc.
- Self-closing Tags: Must be properly closed
- Fragment Support: Can return multiple elements
HTML:
- Markup Language: Static markup
- Static Content: No dynamic expressions
- Lowercase Attributes: class, onclick, etc.
- Optional Closing: Some tags can be unclosed
- Single Root: Must have single root element
Code Example:
// JSX
const UserProfile = ({ user, isActive }) => (
<>
<div className={`profile ${isActive ? 'active' : ''}`}>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={() => console.log('clicked')}>
Edit Profile
</button>
</div>
<div>Additional content</div>
</>
);
// HTML equivalent
<div class="profile active">
<h2>John Doe</h2>
<p>john@example.com</p>
<button onclick="console.log('clicked')">
Edit Profile
</button>
</div>
<div>Additional content</div>
15. Explain component composition and children props
Component composition is a pattern where components are combined to create more complex UIs, often using the children prop.
Benefits: - Reusability: Components can be reused in different contexts - Flexibility: Content can be customized per usage - Separation of Concerns: Logic and presentation are separated
Code Example:
// Layout Component with children
const Card = ({ title, children, actions }) => (
<div className="card">
{title && <h3 className="card-title">{title}</h3>}
<div className="card-content">{children}</div>
{actions && <div className="card-actions">{actions}</div>}
</div>
);
// Usage with composition
const UserDashboard = () => (
<div>
<Card
title="User Profile"
actions={<button>Edit</button>}
>
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
</Card>
<Card title="Recent Activity">
<ul>
<li>Login: 2 hours ago</li>
<li>Profile updated: 1 day ago</li>
</ul>
</Card>
</div>
);
// Advanced composition with render props
const DataCard = ({ title, fetchData, children }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData().then(setData).finally(() => setLoading(false));
}, [fetchData]);
return (
<Card title={title}>
{loading ? (
<div>Loading...</div>
) : (
children(data)
)}
</Card>
);
};
// Usage
<DataCard
title="User Stats"
fetchData={() => fetch('/api/user-stats')}
>
{(data) => (
<div>
<p>Posts: {data.posts}</p>
<p>Followers: {data.followers}</p>
</div>
)}
</DataCard>
16. What are the differences between composition and inheritance?
Composition: - "Has-a" Relationship: Component contains other components - Flexibility: Easy to change behavior by swapping components - Reusability: Components can be reused in different contexts - React Preferred: Recommended approach in React
Inheritance: - "Is-a" Relationship: Component extends another component - Tight Coupling: Changes in parent affect all children - Less Flexible: Harder to modify behavior - Not Recommended: Avoid in React
Code Example:
// Composition (Recommended)
const Button = ({ children, variant = 'primary', ...props }) => (
<button className={`btn btn-${variant}`} {...props}>
{children}
</button>
);
const IconButton = ({ icon, children, ...props }) => (
<Button {...props}>
<span className="icon">{icon}</span>
{children}
</Button>
);
const LoadingButton = ({ loading, children, ...props }) => (
<Button {...props} disabled={loading}>
{loading ? 'Loading...' : children}
</Button>
);
// Usage
<IconButton icon="🚀" onClick={handleLaunch}>
Launch Rocket
</IconButton>
<LoadingButton loading={isSubmitting} onClick={handleSubmit}>
Submit Form
</LoadingButton>
// Inheritance (Not Recommended)
class BaseButton extends React.Component {
render() {
return <button className="base-button">{this.props.children}</button>;
}
}
class IconButton extends BaseButton {
render() {
return (
<button className="icon-button">
<span className="icon">{this.props.icon}</span>
{this.props.children}
</button>
);
}
}
17. Explain React fragments and their usage
React Fragments allow you to group multiple elements without adding extra DOM nodes.
Benefits: - No Extra DOM: Doesn't create additional div elements - Performance: Slightly better performance - Cleaner CSS: No unwanted wrapper elements - Flexibility: Can be used anywhere multiple elements are needed
Code Example:
// Without Fragment (creates extra div)
const UserProfile = ({ user }) => (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button>Edit</button>
</div>
);
// With Fragment (no extra div)
const UserProfile = ({ user }) => (
<>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button>Edit</button>
</>
);
// With explicit Fragment (when you need keys)
const UserList = ({ users }) => (
<>
{users.map(user => (
<React.Fragment key={user.id}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</React.Fragment>
))}
</>
);
// Conditional rendering with fragments
const ConditionalContent = ({ showHeader, showFooter }) => (
<>
{showHeader && <header>Header Content</header>}
<main>Main Content</main>
{showFooter && <footer>Footer Content</footer>}
</>
);
18. What is the difference between fragments and divs?
Fragments: - No DOM Node: Doesn't create actual HTML element - Performance: Slightly better performance - CSS: No impact on CSS layout - Semantic: Doesn't affect HTML semantics
Divs: - DOM Node: Creates actual div element - Layout Impact: Affects CSS layout and styling - Semantic: Adds semantic meaning (container) - Styling: Can be styled and targeted
Code Example:
// Using div (creates extra element)
const UserCard = ({ user }) => (
<div className="user-card">
<div> {/* Extra wrapper div */}
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
</div>
);
// Using fragment (no extra element)
const UserCard = ({ user }) => (
<div className="user-card">
<> {/* No extra element */}
<h2>{user.name}</h2>
<p>{user.email}</p>
</>
</div>
);
// When you need the wrapper (use div)
const UserCard = ({ user }) => (
<div className="user-card">
<div className="user-info"> {/* Needed for styling */}
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
</div>
);
19. Explain React portals and their use cases
React Portals allow you to render children into a DOM node that exists outside the parent component's DOM hierarchy.
Use Cases: - Modals: Render modals outside the main app container - Tooltips: Render tooltips at the document root - Notifications: Render notifications outside the component tree - Third-party Integration: Render content in external containers
Code Example:
// Modal Component using Portal
const Modal = ({ isOpen, onClose, children }) => {
if (!isOpen) return null;
return ReactDOM.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.body // Render to body instead of parent
);
};
// Usage
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="app">
<button onClick={() => setIsModalOpen(true)}>
Open Modal
</button>
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
<h2>Modal Content</h2>
<p>This modal is rendered outside the app container.</p>
</Modal>
</div>
);
};
// Tooltip Component using Portal
const Tooltip = ({ children, content, position = 'top' }) => {
const [isVisible, setIsVisible] = useState(false);
const [coords, setCoords] = useState({ x: 0, y: 0 });
const triggerRef = useRef();
const handleMouseEnter = () => {
const rect = triggerRef.current.getBoundingClientRect();
setCoords({
x: rect.left + rect.width / 2,
y: position === 'top' ? rect.top - 10 : rect.bottom + 10
});
setIsVisible(true);
};
return (
<>
<div
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={() => setIsVisible(false)}
>
{children}
</div>
{isVisible && ReactDOM.createPortal(
<div
className={`tooltip tooltip-${position}`}
style={{
position: 'fixed',
left: coords.x,
top: coords.y,
transform: 'translateX(-50%)'
}}
>
{content}
</div>,
document.body
)}
</>
);
};
20. What are the differences between portals and regular rendering?
Regular Rendering: - Parent Hierarchy: Renders within parent component's DOM tree - CSS Context: Inherits parent's CSS context (overflow, z-index, etc.) - Event Bubbling: Events bubble through parent components - Layout Impact: Affected by parent's layout constraints
Portals: - Independent Hierarchy: Renders outside parent's DOM tree - CSS Isolation: Not affected by parent's CSS context - Event Bubbling: Events still bubble through React component tree - Layout Freedom: Can be positioned anywhere in the DOM
Code Example:
// Regular rendering (constrained by parent)
const App = () => (
<div style={{ overflow: 'hidden', height: '100px' }}>
<div style={{ height: '200px', background: 'red' }}>
This content is clipped by parent's overflow: hidden
</div>
</div>
);
// Portal rendering (not constrained by parent)
const App = () => (
<div style={{ overflow: 'hidden', height: '100px' }}>
<PortalContent />
</div>
);
const PortalContent = () => {
return ReactDOM.createPortal(
<div style={{ height: '200px', background: 'blue' }}>
This content is NOT clipped because it's rendered outside parent
</div>,
document.body
);
};
// Event bubbling still works with portals
const App = () => {
const handleClick = () => console.log('App clicked');
return (
<div onClick={handleClick}>
<PortalButton />
</div>
);
};
const PortalButton = () => {
const handleClick = (e) => {
console.log('Button clicked');
// e.stopPropagation(); // Uncomment to prevent bubbling
};
return ReactDOM.createPortal(
<button onClick={handleClick}>
Click me (rendered via portal)
</button>,
document.body
);
};
React State and State Management
21. Explain React state and its management
Answer: React state is a built-in object that contains data that may change over time and affects the component's rendering. State is managed internally by React and triggers re-renders when updated.
Key Concepts:
- Local State: Component-specific data managed with useState or useReducer
- Global State: Application-wide data shared across components
- Immutable Updates: State should never be mutated directly
- Asynchronous Updates: State updates are batched and may be asynchronous
Coding Example:
import React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
const UserProfile: React.FC = () => {
// Local state management
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// State update patterns
const updateUserName = (newName: string) => {
setUser(prevUser => {
if (!prevUser) return null;
return { ...prevUser, name: newName };
});
};
const fetchUser = async (userId: number) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch user');
} finally {
setLoading(false);
}
};
return (
<div>
{loading && <div>Loading...</div>}
{error && <div>Error: {error}</div>}
{user && (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={() => updateUserName('New Name')}>
Update Name
</button>
</div>
)}
</div>
);
};
22. What are the differences between useState and useReducer?
Answer:
| Aspect | useState | useReducer |
|---|---|---|
| Complexity | Simple state updates | Complex state logic |
| State Structure | Single value or object | Complex state object |
| Update Logic | Direct value assignment | Action-based updates |
| Performance | Good for simple cases | Better for complex state |
| Testing | Straightforward | More testable with actions |
| Debugging | Simple | Better with action logging |
Coding Example:
import React, { useState, useReducer } from 'react';
// useState approach
const CounterWithState: React.FC = () => {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
};
// useReducer approach
interface CounterState {
count: number;
loading: boolean;
history: number[];
}
type CounterAction =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' }
| { type: 'SET_LOADING'; payload: boolean };
const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1,
history: [...state.history, state.count + 1]
};
case 'DECREMENT':
return {
...state,
count: state.count - 1,
history: [...state.history, state.count - 1]
};
case 'RESET':
return {
...state,
count: 0,
history: [...state.history, 0]
};
case 'SET_LOADING':
return { ...state, loading: action.payload };
default:
return state;
}
};
const CounterWithReducer: React.FC = () => {
const [state, dispatch] = useReducer(counterReducer, {
count: 0,
loading: false,
history: []
});
return (
<div>
<p>Count: {state.count}</p>
<p>History: {state.history.join(', ')}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
);
};
23. Explain React context and its usage
Answer: React Context provides a way to pass data through the component tree without having to pass props down manually at every level. It's useful for sharing data that is considered "global" for a tree of React components.
Key Concepts: - Provider: Wraps components and provides data - Consumer: Components that consume the context data - useContext Hook: Modern way to consume context - Performance: Context triggers re-renders for all consuming components
Coding Example:
import React, { createContext, useContext, useState, ReactNode } from 'react';
// Define context types
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
interface UserContextType {
user: User | null;
login: (user: User) => void;
logout: () => void;
}
// Create contexts
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
const UserContext = createContext<UserContextType | undefined>(undefined);
// Custom hooks for using contexts
const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
const useUser = (): UserContextType => {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
};
// Provider components
const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const login = (userData: User) => setUser(userData);
const logout = () => setUser(null);
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
};
// Consumer components
const Header: React.FC = () => {
const { theme, toggleTheme } = useTheme();
const { user, logout } = useUser();
return (
<header className={`header-${theme}`}>
<h1>My App</h1>
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
{user && (
<div>
<span>Welcome, {user.name}</span>
<button onClick={logout}>Logout</button>
</div>
)}
</header>
);
};
const App: React.FC = () => {
return (
<ThemeProvider>
<UserProvider>
<Header />
<MainContent />
</UserProvider>
</ThemeProvider>
);
};
24. What is the difference between context and props drilling?
Answer:
| Aspect | Props Drilling | Context API |
|---|---|---|
| Complexity | Manual prop passing through each level | Automatic data sharing |
| Maintenance | Hard to maintain as app grows | Easier to maintain |
| Performance | Re-renders only direct children | Re-renders all consumers |
| Readability | Props visible in component signature | Hidden data flow |
| Use Case | Simple, shallow component trees | Deep component trees |
Coding Example:
// Props Drilling Approach
const App: React.FC = () => {
const [user, setUser] = useState<User | null>(null);
const [theme, setTheme] = useState<'light' | 'dark'>('light');
return (
<div>
<Header user={user} theme={theme} onThemeChange={setTheme} />
<MainContent user={user} theme={theme} />
<Sidebar user={user} theme={theme} />
</div>
);
};
const Header: React.FC<{
user: User | null;
theme: 'light' | 'dark';
onThemeChange: (theme: 'light' | 'dark') => void;
}> = ({ user, theme, onThemeChange }) => {
return (
<header>
<UserInfo user={user} />
<ThemeToggle theme={theme} onThemeChange={onThemeChange} />
</header>
);
};
// Context API Approach
const App: React.FC = () => {
return (
<UserProvider>
<ThemeProvider>
<Header />
<MainContent />
<Sidebar />
</ThemeProvider>
</UserProvider>
);
};
const Header: React.FC = () => {
const { user } = useUser();
const { theme, toggleTheme } = useTheme();
return (
<header>
<UserInfo />
<ThemeToggle />
</header>
);
};
25. Explain Redux and its architecture
Answer: Redux is a predictable state container for JavaScript applications. It follows a unidirectional data flow pattern with three core principles: 1. Single source of truth - State is stored in one place 2. State is read-only - Only actions can change state 3. Changes are made with pure functions - Reducers are pure functions
Architecture Components: - Store: Single source of truth for application state - Actions: Plain objects describing what happened - Reducers: Pure functions that specify how state changes - Dispatch: Function to send actions to the store - Selectors: Functions to extract specific data from state
Coding Example:
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';
import thunk from 'redux-thunk';
// Action Types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const SET_FILTER = 'SET_FILTER';
// Action Creators
const addTodo = (text: string) => ({
type: ADD_TODO,
payload: { id: Date.now(), text, completed: false }
});
const toggleTodo = (id: number) => ({
type: TOGGLE_TODO,
payload: id
});
const setFilter = (filter: 'all' | 'active' | 'completed') => ({
type: SET_FILTER,
payload: filter
});
// Reducers
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
}
const initialState: TodoState = {
todos: [],
filter: 'all'
};
const todoReducer = (state = initialState, action: any): TodoState => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload]
};
case TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case SET_FILTER:
return {
...state,
filter: action.payload
};
default:
return state;
}
};
// Store
const rootReducer = combineReducers({
todos: todoReducer
});
const store = createStore(rootReducer, applyMiddleware(thunk));
// Selectors
const selectTodos = (state: any) => state.todos.todos;
const selectFilter = (state: any) => state.todos.filter;
const selectFilteredTodos = (state: any) => {
const todos = selectTodos(state);
const filter = selectFilter(state);
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
};
// Components
const TodoApp: React.FC = () => {
return (
<Provider store={store}>
<TodoList />
<TodoFilters />
</Provider>
);
};
const TodoList: React.FC = () => {
const todos = useSelector(selectFilteredTodos);
const dispatch = useDispatch();
const handleAddTodo = (text: string) => {
dispatch(addTodo(text));
};
const handleToggleTodo = (id: number) => {
dispatch(toggleTodo(id));
};
return (
<div>
<AddTodoForm onAdd={handleAddTodo} />
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => handleToggleTodo(todo.id)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
</div>
);
};
26. What are the differences between Redux and Context API?
Answer:
| Aspect | Redux | Context API |
|---|---|---|
| Complexity | More complex setup | Simpler setup |
| Bundle Size | Larger bundle | Smaller bundle |
| DevTools | Excellent debugging tools | Limited debugging |
| Performance | Optimized with selectors | Can cause unnecessary re-renders |
| Middleware | Built-in middleware support | No middleware |
| Predictability | Very predictable | Less predictable |
| Use Case | Large applications | Small to medium applications |
Coding Example:
// Redux Approach
const TodoItem: React.FC<{ id: number }> = ({ id }) => {
const todo = useSelector(state =>
state.todos.todos.find(t => t.id === id)
);
const dispatch = useDispatch();
// Only re-renders when this specific todo changes
return (
<li onClick={() => dispatch(toggleTodo(id))}>
{todo?.text}
</li>
);
};
// Context API Approach
const TodoItem: React.FC<{ id: number }> = ({ id }) => {
const { todos, toggleTodo } = useContext(TodoContext);
const todo = todos.find(t => t.id === id);
// Re-renders when any todo changes
return (
<li onClick={() => toggleTodo(id)}>
{todo?.text}
</li>
);
};
27. Explain Redux Toolkit and its benefits
Answer: Redux Toolkit (RTK) is the official, opinionated, batteries-included toolset for efficient Redux development. It simplifies Redux setup and reduces boilerplate code.
Benefits:
- Reduces boilerplate with createSlice
- Built-in Immer for immutable updates
- Automatic action creators and action types
- DevTools integration out of the box
- TypeScript support with better type inference
- RTK Query for API state management
Coding Example:
import { createSlice, createAsyncThunk, configureStore } from '@reduxjs/toolkit';
import { useSelector, useDispatch } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Async thunk for API calls
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async () => {
const response = await fetch('/api/todos');
return response.json();
}
);
export const addTodoAsync = createAsyncThunk(
'todos/addTodo',
async (text: string) => {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
return response.json();
}
);
// Slice with automatic action creators
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [] as Todo[],
loading: false,
error: null as string | null
},
reducers: {
toggleTodo: (state, action) => {
const todo = state.items.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed; // Immer handles immutability
}
},
setFilter: (state, action) => {
state.filter = action.payload;
}
},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.loading = true;
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || 'Failed to fetch todos';
})
.addCase(addTodoAsync.fulfilled, (state, action) => {
state.items.push(action.payload);
});
}
});
// Auto-generated action creators
export const { toggleTodo, setFilter } = todosSlice.actions;
// Store configuration
export const store = configureStore({
reducer: {
todos: todosSlice.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST']
}
})
});
// TypeScript types
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Typed hooks
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector = <T>(selector: (state: RootState) => T) =>
useSelector<RootState, T>(selector);
// Component usage
const TodoList: React.FC = () => {
const todos = useAppSelector(state => state.todos.items);
const loading = useAppSelector(state => state.todos.loading);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
const handleAddTodo = (text: string) => {
dispatch(addTodoAsync(text));
};
if (loading) return <div>Loading...</div>;
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</div>
);
};
28. What is the difference between Redux Toolkit and plain Redux?
Answer:
| Aspect | Plain Redux | Redux Toolkit |
|---|---|---|
| Setup | Manual store configuration | configureStore with defaults |
| Reducers | Manual switch statements | createSlice with auto-generated actions |
| Immutability | Manual spread operators | Built-in Immer |
| Async Actions | Manual thunk setup | createAsyncThunk |
| DevTools | Manual configuration | Automatic setup |
| Boilerplate | High | Low |
| TypeScript | Manual type definitions | Better type inference |
Coding Example:
// Plain Redux
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const addTodo = (text: string) => ({
type: ADD_TODO,
payload: { id: Date.now(), text, completed: false }
});
const todoReducer = (state = [], action: any) => {
switch (action.type) {
case ADD_TODO:
return [...state, action.payload];
case TOGGLE_TODO:
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
default:
return state;
}
};
// Redux Toolkit
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push(action.payload); // Immer handles immutability
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
}
}
});
export const { addTodo, toggleTodo } = todosSlice.actions;
29. Explain state management patterns in React
Answer: React state management patterns help organize and structure state in applications. Common patterns include:
1. Container/Presentational Pattern:
// Container Component (Smart Component)
const UserListContainer: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
setLoading(true);
try {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
} catch (error) {
console.error('Failed to fetch users:', error);
} finally {
setLoading(false);
}
};
return <UserList users={users} loading={loading} />;
};
// Presentational Component (Dumb Component)
const UserList: React.FC<{ users: User[]; loading: boolean }> = ({
users,
loading
}) => {
if (loading) return <div>Loading...</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
2. Custom Hook Pattern:
const useUsers = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchUsers = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch users');
} finally {
setLoading(false);
}
}, []);
const addUser = useCallback(async (user: Omit<User, 'id'>) => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
});
const newUser = await response.json();
setUsers(prev => [...prev, newUser]);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to add user');
}
}, []);
return { users, loading, error, fetchUsers, addUser };
};
const UserManagement: React.FC = () => {
const { users, loading, error, fetchUsers, addUser } = useUsers();
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
return (
<div>
{error && <div>Error: {error}</div>}
<UserList users={users} loading={loading} />
<AddUserForm onAdd={addUser} />
</div>
);
};
3. Render Props Pattern:
interface DataFetcherProps<T> {
url: string;
children: (data: T[], loading: boolean, error: string | null) => ReactNode;
}
const DataFetcher = <T,>({ url, children }: DataFetcherProps<T>) => {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch data');
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return <>{children(data, loading, error)}</>;
};
const UserListWithRenderProps: React.FC = () => {
return (
<DataFetcher<User> url="/api/users">
{(users, loading, error) => (
<div>
{error && <div>Error: {error}</div>}
{loading && <div>Loading...</div>}
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
)}
</DataFetcher>
);
};
30. What are the differences between local and global state?
Answer:
| Aspect | Local State | Global State |
|---|---|---|
| Scope | Component-specific | Application-wide |
| Lifetime | Component lifecycle | Application lifecycle |
| Sharing | Not shared between components | Shared across components |
| Performance | Minimal impact | Can affect multiple components |
| Complexity | Simple to manage | More complex management |
| Use Cases | Form inputs, UI state | User authentication, theme |
Coding Example:
// Local State Example
const UserForm: React.FC = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [errors, setErrors] = useState<Record<string, string>>({});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Validate and submit form
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
};
// Global State Example
const App: React.FC = () => {
return (
<AuthProvider>
<ThemeProvider>
<Header />
<MainContent />
<Sidebar />
</ThemeProvider>
</AuthProvider>
);
};
const Header: React.FC = () => {
const { user, logout } = useAuth(); // Global state
const { theme, toggleTheme } = useTheme(); // Global state
return (
<header>
{user && <span>Welcome, {user.name}</span>}
<button onClick={toggleTheme}>Toggle Theme</button>
<button onClick={logout}>Logout</button>
</header>
);
};
React Hooks
31. Explain React hooks and their rules
Answer: React hooks are functions that allow you to use state and other React features in functional components. They were introduced in React 16.8.
Rules of Hooks: 1. Only call hooks at the top level - Don't call hooks inside loops, conditions, or nested functions 2. Only call hooks from React functions - Call hooks from React function components or custom hooks 3. Hook names must start with "use" - This is a naming convention for custom hooks
Coding Example:
import React, { useState, useEffect, useCallback, useMemo } from 'react';
// ✅ Correct: Hooks at top level
const UserProfile: React.FC = () => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchUser();
}, []);
const fetchUser = useCallback(async () => {
setLoading(true);
try {
const response = await fetch('/api/user');
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
}, []);
const userDisplayName = useMemo(() => {
if (!user) return 'Guest';
return `${user.firstName} ${user.lastName}`;
}, [user]);
return (
<div>
{loading ? (
<div>Loading...</div>
) : (
<div>
<h2>Welcome, {userDisplayName}</h2>
{user && <UserDetails user={user} />}
</div>
)}
</div>
);
};
// ❌ Incorrect: Hooks inside conditions
const BadComponent: React.FC = () => {
const [count, setCount] = useState(0);
if (count > 0) {
useEffect(() => {
// This violates the rules of hooks
console.log('Count is greater than 0');
}, [count]);
}
return <div>{count}</div>;
};
// ✅ Correct: Custom hook
const useUser = (userId: number) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch user');
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
return { user, loading, error };
};
32. What are the differences between useState and useEffect?
Answer:
| Aspect | useState | useEffect |
|---|---|---|
| Purpose | State management | Side effects |
| Return Value | Current state and setter | Nothing (void) |
| Dependencies | No dependencies array | Optional dependencies array |
| Execution | Synchronous | Asynchronous (after render) |
| Use Cases | Component state | API calls, subscriptions, DOM manipulation |
Coding Example:
const UserDashboard: React.FC = () => {
// useState for component state
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<'profile' | 'settings'>('profile');
// useEffect for side effects
useEffect(() => {
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch('/api/user');
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch user');
} finally {
setLoading(false);
}
};
fetchUser();
}, []); // Empty dependency array = run once on mount
// useEffect with dependencies
useEffect(() => {
if (user) {
document.title = `Dashboard - ${user.name}`;
}
}, [user]); // Runs when user changes
// useEffect for cleanup
useEffect(() => {
const interval = setInterval(() => {
console.log('Checking for updates...');
}, 30000);
return () => {
clearInterval(interval); // Cleanup on unmount
};
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h1>Welcome, {user.name}</h1>
<div>
<button
onClick={() => setActiveTab('profile')}
className={activeTab === 'profile' ? 'active' : ''}
>
Profile
</button>
<button
onClick={() => setActiveTab('settings')}
className={activeTab === 'settings' ? 'active' : ''}
>
Settings
</button>
</div>
{activeTab === 'profile' ? (
<UserProfile user={user} />
) : (
<UserSettings user={user} />
)}
</div>
);
};
33. Explain useEffect hook and its dependencies
useEffect synchronizes a committed component with an external system. List every reactive dependency; cleanup runs before the effect is re-established after dependency changes and on unmount. Strict Mode adds a development-only setup/cleanup check. An empty dependency array does not promise a single execution over the application lifetime. Effects do not run during server rendering. Reference: useEffect.
34. What is the difference between useEffect and componentDidMount?
useEffect is not a direct replacement for componentDidMount. Effects synchronize with external systems and re-run when their declared dependencies change. For a mount-only external subscription, include a cleanup; be aware that Strict Mode performs an additional development setup/cleanup cycle. Reference: useEffect.
35. Explain custom hooks and their creation
Answer: Custom hooks are JavaScript functions that start with "use" and can call other hooks. They allow you to extract component logic into reusable functions.
Key Points: - Must start with "use" (React's naming convention) - Can use other hooks internally - Share stateful logic between components - Follow the same rules as regular hooks
Coding Example:
// Custom hook for API calls with loading state
import { useState, useEffect } from 'react';
interface UseApiResult<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
function useApi<T>(url: string): UseApiResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// Usage in component
function UserProfile({ userId }: { userId: string }) {
const { data: user, loading, error, refetch } = useApi<User>(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h1>{user.name}</h1>
<button onClick={refetch}>Refresh</button>
</div>
);
}
36. What are the differences between custom hooks and regular functions?
Answer:
| Aspect | Custom Hooks | Regular Functions |
|---|---|---|
| Naming Convention | Must start with "use" | Any valid function name |
| Hook Usage | Can call other hooks | Cannot call hooks |
| State Management | Can use useState, useEffect, etc. | Cannot use React hooks |
| Re-rendering | Can trigger component re-renders | Cannot trigger re-renders |
| Lifecycle Integration | Integrate with React lifecycle | No lifecycle integration |
| Return Values | Can return state, functions, or any value | Can return any value |
| Component Context | Must be called inside React components | Can be called anywhere |
Coding Example:
// Custom Hook - Can use React hooks
function useCounter(initialValue: number = 0) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
const decrement = useCallback(() => {
setCount(prev => prev - 1);
}, []);
return { count, increment, decrement };
}
// Regular Function - Cannot use React hooks
function calculateTotal(items: number[]): number {
return items.reduce((sum, item) => sum + item, 0);
}
// Usage
function MyComponent() {
const { count, increment, decrement } = useCounter(0); // ✅ Works
const total = calculateTotal([1, 2, 3]); // ✅ Works
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<p>Total: {total}</p>
</div>
);
}
37. Explain useCallback and useMemo hooks
React.memo is a performance optimization that can skip rendering when props compare equal (Object.is by default). State or consumed context can still trigger updates, and React may render anyway. useMemo caches a calculation result; useCallback caches a function identity. None guarantees correctness or should be required to keep an application working. Measure before adding memoization. Reference: memo.
38. What is the difference between useCallback and useMemo?
Answer:
| Aspect | useCallback | useMemo |
|---|---|---|
| Purpose | Memoizes functions | Memoizes computed values |
| Return Type | Returns a memoized function | Returns a memoized value |
| Use Case | Prevent child re-renders due to function prop changes | Avoid expensive recalculations |
| Performance Impact | Reduces unnecessary re-renders | Reduces computational overhead |
| Dependency Array | Controls when function is recreated | Controls when value is recalculated |
Coding Example:
import React, { useState, useCallback, useMemo } from 'react';
function PerformanceExample() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
// useCallback - memoizes function
const handleClick = useCallback((id: number) => {
console.log(`Clicked item ${id}`);
setItems(prev => prev.filter(item => item !== id));
}, []); // Function reference stays the same
// useMemo - memoizes computed value
const expensiveCalculation = useMemo(() => {
console.log('Performing expensive calculation...');
return items.reduce((sum, item) => {
// Simulate expensive operation
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += item * Math.random();
}
return sum + result;
}, 0);
}, [items]); // Only recalculate when items change
// useMemo for derived state
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a - b);
}, [items]);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(c => c + 1)}>
Update Count (triggers re-render)
</button>
<h3>Expensive Result: {expensiveCalculation.toFixed(2)}</h3>
<h3>Sorted Items: {sortedItems.join(', ')}</h3>
<div>
{items.map(item => (
<button key={item} onClick={() => handleClick(item)}>
Remove {item}
</button>
))}
</div>
</div>
);
}
39. Explain useRef hook and its use cases
Answer: useRef returns a mutable object with a .current property that persists across re-renders without causing re-renders when changed.
Use Cases: 1. DOM element references 2. Storing mutable values 3. Previous value tracking 4. Timer/interval management 5. Imperative API access
Coding Example:
import React, { useRef, useEffect, useState } from 'react';
function UseRefExamples() {
const inputRef = useRef<HTMLInputElement>(null);
const countRef = useRef<number>(0);
const prevCountRef = useRef<number>(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const [count, setCount] = useState(0);
// 1. DOM element reference
const focusInput = () => {
inputRef.current?.focus();
};
// 2. Storing mutable values
const incrementCount = () => {
countRef.current += 1;
console.log('Internal count:', countRef.current);
};
// 3. Previous value tracking
useEffect(() => {
prevCountRef.current = count;
}, [count]);
// 4. Timer management
const startTimer = () => {
if (intervalRef.current) return; // Prevent multiple timers
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
// Cleanup on unmount
useEffect(() => {
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);
return (
<div>
<h2>useRef Examples</h2>
{/* DOM Reference */}
<div>
<input ref={inputRef} placeholder="Focus me" />
<button onClick={focusInput}>Focus Input</button>
</div>
{/* Mutable Value */}
<div>
<p>State Count: {count}</p>
<p>Previous Count: {prevCountRef.current}</p>
<button onClick={incrementCount}>Increment Internal Count</button>
<button onClick={() => setCount(c => c + 1)}>Increment State</button>
</div>
{/* Timer Management */}
<div>
<button onClick={startTimer}>Start Timer</button>
<button onClick={stopTimer}>Stop Timer</button>
</div>
</div>
);
}
// Custom hook for previous value
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
// Usage
function CounterWithPrevious() {
const [count, setCount] = useState(0);
const previousCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {previousCount}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
40. What are the differences between useRef and useState?
Answer:
| Aspect | useRef | useState |
|---|---|---|
| Re-rendering | Changes don't trigger re-renders | Changes trigger re-renders |
| Value Access | .current property |
Direct value access |
| Update Method | Direct assignment to .current |
Setter function |
| Initial Value | Passed as argument | Passed as argument |
| Use Case | Mutable values, DOM refs | Reactive state |
| Synchronous Updates | Updates are synchronous | Updates are asynchronous |
| Previous Value Access | Can access previous value | Cannot directly access previous |
Coding Example:
import React, { useRef, useState, useEffect } from 'react';
function RefVsStateComparison() {
const [stateCount, setStateCount] = useState(0);
const refCount = useRef(0);
const [renderCount, setRenderCount] = useState(0);
// Force re-render to see differences
const forceRender = () => {
setRenderCount(c => c + 1);
};
const updateState = () => {
setStateCount(c => c + 1);
console.log('State updated, component will re-render');
};
const updateRef = () => {
refCount.current += 1;
console.log('Ref updated, no re-render:', refCount.current);
};
const updateBoth = () => {
setStateCount(c => c + 1);
refCount.current += 1;
};
useEffect(() => {
console.log('Component rendered');
});
return (
<div>
<h2>useRef vs useState Comparison</h2>
<div>
<h3>State Count: {stateCount}</h3>
<h3>Ref Count: {refCount.current}</h3>
<h3>Render Count: {renderCount}</h3>
</div>
<div>
<button onClick={updateState}>Update State Only</button>
<button onClick={updateRef}>Update Ref Only</button>
<button onClick={updateBoth}>Update Both</button>
<button onClick={forceRender}>Force Re-render</button>
</div>
<div>
<p><strong>Key Differences:</strong></p>
<ul>
<li>State changes trigger re-renders, ref changes don't</li>
<li>State updates are asynchronous, ref updates are synchronous</li>
<li>State is reactive, ref is mutable</li>
<li>State is for UI data, ref is for non-reactive data</li>
</ul>
</div>
</div>
);
}
41. Explain React component lifecycle in functional components
Function components render from props and state. Effects run after commit to synchronize with external systems; cleanup runs before a dependent effect re-runs and on unmount. Think in terms of independent synchronization processes instead of mapping every class lifecycle method to an effect. Reference: useEffect.
42. What are the differences between useEffect and lifecycle methods?
Answer:
| Aspect | useEffect | Class Lifecycle Methods |
|---|---|---|
| Paradigm | Functional programming | Object-oriented |
| Composition | Can have multiple effects | One method per lifecycle phase |
| Cleanup | Return function from effect | Separate cleanup methods |
| Dependencies | Dependency array controls execution | Always runs on lifecycle |
| Timing | Runs after render | Runs at specific lifecycle points |
| Flexibility | More flexible and composable | Less flexible |
Coding Example:
// Functional Component with useEffect
function FunctionalComponent({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
// Equivalent to componentDidMount + componentDidUpdate
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
setLoading(false);
};
fetchUser();
}, [userId]);
// Equivalent to componentWillUnmount
useEffect(() => {
return () => {
console.log('Component unmounting');
};
}, []);
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}
// Class Component with lifecycle methods
class ClassComponent extends React.Component<{ userId: string }, { user: User | null, loading: boolean }> {
constructor(props: { userId: string }) {
super(props);
this.state = { user: null, loading: true };
}
async componentDidMount() {
await this.fetchUser();
}
async componentDidUpdate(prevProps: { userId: string }) {
if (prevProps.userId !== this.props.userId) {
await this.fetchUser();
}
}
componentWillUnmount() {
console.log('Component unmounting');
}
async fetchUser() {
const response = await fetch(`/api/users/${this.props.userId}`);
const userData = await response.json();
this.setState({ user: userData, loading: false });
}
render() {
if (this.state.loading) return <div>Loading...</div>;
return <div>{this.state.user?.name}</div>;
}
}
43. Explain cleanup functions in useEffect
An effect cleanup function undoes subscriptions, timers, connections, or other external work created by that effect. It runs before the next setup after a dependency change and when the component unmounts. Do not use cleanup only as a generic “setState prevention” technique; design cancellation and stale-result handling for the request.
44. What is the difference between cleanup and unmounting?
Answer:
| Aspect | Cleanup Function | Unmounting |
|---|---|---|
| Timing | Runs before unmount OR before effect re-runs | Component is removed from DOM |
| Frequency | Can run multiple times | Only runs once |
| Trigger | Effect dependency changes or unmount | Only component removal |
| Purpose | Clean up effect-specific resources | Final component cleanup |
| Scope | Effect-specific cleanup | Component-wide cleanup |
Coding Example:
import React, { useState, useEffect } from 'react';
function CleanupVsUnmount({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
// Effect with cleanup - runs when userId changes OR on unmount
useEffect(() => {
console.log(`Fetching user ${userId}`);
let isMounted = true;
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (isMounted) {
setUser(userData);
}
};
fetchUser();
// Cleanup function - runs before effect re-runs OR on unmount
return () => {
console.log(`Cleanup for user ${userId}`);
isMounted = false;
};
}, [userId]); // Dependency array - cleanup runs when userId changes
// Unmount-only effect
useEffect(() => {
// This only runs when component unmounts
return () => {
console.log('Component unmounting - final cleanup');
};
}, []); // Empty dependency array
return (
<div>
<h2>User: {user?.name || 'Loading...'}</h2>
<p>Current User ID: {userId}</p>
</div>
);
}
// Parent component to demonstrate
function ParentComponent() {
const [currentUserId, setCurrentUserId] = useState('1');
const [showComponent, setShowComponent] = useState(true);
return (
<div>
<button onClick={() => setCurrentUserId('1')}>User 1</button>
<button onClick={() => setCurrentUserId('2')}>User 2</button>
<button onClick={() => setShowComponent(!showComponent)}>
{showComponent ? 'Hide' : 'Show'} Component
</button>
{showComponent && <CleanupVsUnmount userId={currentUserId} />}
</div>
);
}
45. Explain dependency arrays in useEffect
Answer: Dependency arrays control when useEffect runs by specifying which values the effect depends on.
Rules:
- Empty array [] - Effect runs only once (mount)
- No array - Effect runs after every render
- Populated array [dep1, dep2] - Effect runs when dependencies change
- Object/function dependencies - Use useCallback/useMemo to stabilize
Coding Example:
import React, { useState, useEffect, useCallback, useMemo } from 'react';
function DependencyArrayExamples() {
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
const [searchTerm, setSearchTerm] = useState('');
// 1. Empty dependency array - runs only once
useEffect(() => {
console.log('Component mounted - runs only once');
// Initial setup, subscriptions, etc.
}, []);
// 2. No dependency array - runs after every render
useEffect(() => {
console.log('Component rendered - runs every time');
// Be careful with this - can cause infinite loops
});
// 3. Populated dependency array - runs when dependencies change
useEffect(() => {
console.log(`Count changed to: ${count}`);
// Effect runs when count changes
}, [count]);
// 4. Multiple dependencies
useEffect(() => {
if (user && searchTerm) {
console.log(`Searching for "${searchTerm}" in user ${user.id}`);
}
}, [user, searchTerm]);
// 5. Object dependency - problematic
const userConfig = { id: user?.id, name: user?.name };
useEffect(() => {
console.log('User config changed');
}, [userConfig]); // ❌ This will run on every render!
// 6. Object dependency - fixed with useMemo
const memoizedUserConfig = useMemo(() => ({
id: user?.id,
name: user?.name
}), [user?.id, user?.name]);
useEffect(() => {
console.log('Memoized user config changed');
}, [memoizedUserConfig]); // ✅ Only runs when user.id or user.name changes
// 7. Function dependency - problematic
const handleUserUpdate = (newUser: User) => {
setUser(newUser);
};
useEffect(() => {
console.log('Handle user update function changed');
}, [handleUserUpdate]); // ❌ This will run on every render!
// 8. Function dependency - fixed with useCallback
const memoizedHandleUserUpdate = useCallback((newUser: User) => {
setUser(newUser);
}, []); // Empty dependency array - function never changes
useEffect(() => {
console.log('Memoized handle user update function changed');
}, [memoizedHandleUserUpdate]); // ✅ Only runs once
return (
<div>
<h2>Dependency Array Examples</h2>
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search term"
/>
</div>
);
}
// Custom hook with proper dependencies
function useUserData(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (isMounted) {
setUser(userData);
}
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
return () => {
isMounted = false;
};
}, [userId]); // ✅ Proper dependency
return { user, loading };
}
46. What are the differences between empty and populated dependency arrays?
Answer:
| Aspect | Empty Array [] |
Populated Array [dep1, dep2] |
|---|---|---|
| Execution Frequency | Runs only once (mount) | Runs when dependencies change |
| Use Case | Initial setup, subscriptions | Reactive effects |
| Cleanup | Runs only on unmount | Runs before effect re-runs |
| Performance | Most performant | Depends on dependency changes |
| Common Patterns | API calls, event listeners | Data fetching, calculations |
Coding Example:
import React, { useState, useEffect } from 'react';
function DependencyArrayComparison() {
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
const [userId, setUserId] = useState('1');
// Empty dependency array - runs only once
useEffect(() => {
console.log('�� Empty array effect - runs only once');
// Initial setup
document.title = 'My App';
// Event listener that should persist
const handleKeyPress = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
console.log('Escape pressed');
}
};
window.addEventListener('keydown', handleKeyPress);
// Cleanup only runs on unmount
return () => {
console.log('🟢 Empty array cleanup - only on unmount');
window.removeEventListener('keydown', handleKeyPress);
};
}, []); // Empty array
// Populated dependency array - runs when dependencies change
useEffect(() => {
console.log('🔵 Populated array effect - runs when userId changes');
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('Failed to fetch user:', error);
}
};
fetchUser();
// Cleanup runs before effect re-runs
return () => {
console.log('🔵 Populated array cleanup - before re-run');
};
}, [userId]); // Populated array
// Multiple dependencies
useEffect(() => {
console.log('🟡 Multiple dependencies effect');
console.log(`Count: ${count}, User: ${user?.name}`);
}, [count, user?.name]); // Multiple dependencies
return (
<div>
<h2>Dependency Array Comparison</h2>
<div>
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
<button onClick={() => setUserId(u => u === '1' ? '2' : '1')}>
Switch User (Current: {userId})
</button>
</div>
<div>
<h3>User: {user?.name || 'Loading...'}</h3>
<p>Check console for effect execution logs</p>
</div>
<div>
<h4>Key Differences:</h4>
<ul>
<li><strong>Empty Array:</strong> One-time setup, cleanup only on unmount</li>
<li><strong>Populated Array:</strong> Reactive effects, cleanup before re-runs</li>
<li><strong>Empty Array:</strong> Best for subscriptions, event listeners</li>
<li><strong>Populated Array:</strong> Best for data fetching, calculations</li>
</ul>
</div>
</div>
);
}
47. Explain multiple useEffect hooks in a component
Answer: Multiple useEffect hooks allow you to separate concerns and organize different side effects logically.
Benefits: 1. Separation of concerns 2. Better readability 3. Independent dependency management 4. Easier testing and debugging
Coding Example:
import React, { useState, useEffect } from 'react';
function MultipleUseEffects({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
const [online, setOnline] = useState(navigator.onLine);
// Effect 1: User data fetching
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (isMounted) {
setUser(userData);
}
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
return () => {
isMounted = false;
};
}, [userId]);
// Effect 2: User posts fetching (depends on user)
useEffect(() => {
if (!user) return;
let isMounted = true;
const fetchPosts = async () => {
try {
const response = await fetch(`/api/users/${user.id}/posts`);
const postsData = await response.json();
if (isMounted) {
setPosts(postsData);
}
} catch (error) {
console.error('Failed to fetch posts:', error);
}
};
fetchPosts();
return () => {
isMounted = false;
};
}, [user]);
// Effect 3: Window size tracking
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
// Set initial size
handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Empty dependency array - only setup once
// Effect 4: Online/offline status
useEffect(() => {
const handleOnline = () => setOnline(true);
const handleOffline = () => setOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []); // Empty dependency array - only setup once
// Effect 5: Document title update
useEffect(() => {
if (user) {
document.title = `${user.name} - Profile`;
} else {
document.title = 'Loading...';
}
}, [user?.name]);
// Effect 6: Analytics tracking
useEffect(() => {
if (user) {
// Track user profile view
analytics.track('profile_viewed', {
userId: user.id,
userName: user.name
});
}
}, [user?.id, user?.name]);
if (loading) return <div>Loading...</div>;
return (
<div>
<h2>Multiple useEffect Example</h2>
<div>
<h3>User: {user?.name}</h3>
<p>Posts: {posts.length}</p>
<p>Window: {windowSize.width} x {windowSize.height}</p>
<p>Status: {online ? '🟢 Online' : '🔴 Offline'}</p>
</div>
<div>
<h4>Effect Organization:</h4>
<ul>
<li><strong>Effect 1:</strong> User data fetching</li>
<li><strong>Effect 2:</strong> Posts fetching (depends on user)</li>
<li><strong>Effect 3:</strong> Window size tracking</li>
<li><strong>Effect 4:</strong> Online/offline status</li>
<li><strong>Effect 5:</strong> Document title updates</li>
<li><strong>Effect 6:</strong> Analytics tracking</li>
</ul>
</div>
</div>
);
}
// Custom hooks for better organization
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setOnline(true);
const handleOffline = () => setOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return online;
}
// Refactored component using custom hooks
function RefactoredComponent({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const windowSize = useWindowSize();
const online = useOnlineStatus();
// User fetching effect
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (isMounted) {
setUser(userData);
}
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
return () => {
isMounted = false;
};
}, [userId]);
// Posts fetching effect
useEffect(() => {
if (!user) return;
let isMounted = true;
const fetchPosts = async () => {
try {
const response = await fetch(`/api/users/${user.id}/posts`);
const postsData = await response.json();
if (isMounted) {
setPosts(postsData);
}
} catch (error) {
console.error('Failed to fetch posts:', error);
}
};
fetchPosts();
return () => {
isMounted = false;
};
}, [user]);
if (loading) return <div>Loading...</div>;
return (
<div>
<h2>Refactored Component</h2>
<h3>User: {user?.name}</h3>
<p>Posts: {posts.length}</p>
<p>Window: {windowSize.width} x {windowSize.height}</p>
<p>Status: {online ? '🟢 Online' : '🔴 Offline'}</p>
</div>
);
}
48. What is the difference between multiple effects and single effect?
Answer:
Multiple effects vs single effect refers to how you organize useEffect hooks in React components.
Single Effect Approach:
// Single effect handling multiple concerns
useEffect(() => {
// Fetch user data
fetchUserData();
// Set up event listeners
const handleResize = () => setWindowSize(window.innerWidth);
window.addEventListener('resize', handleResize);
// Set up timers
const timer = setInterval(() => {
updateTimestamp();
}, 1000);
return () => {
window.removeEventListener('resize', handleResize);
clearInterval(timer);
};
}, []);
Multiple Effects Approach:
// Separate effects for different concerns
useEffect(() => {
fetchUserData();
}, []);
useEffect(() => {
const handleResize = () => setWindowSize(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useEffect(() => {
const timer = setInterval(() => {
updateTimestamp();
}, 1000);
return () => clearInterval(timer);
}, []);
Benefits of Multiple Effects: - Better separation of concerns - Easier to understand and maintain - Independent dependency arrays - More granular cleanup
49. Explain useEffect timing and execution order
Answer:
useEffect execution follows a specific order in React's lifecycle:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// 1. Runs after every render (no dependency array)
useEffect(() => {
console.log('Component rendered');
});
// 2. Runs only after mount (empty dependency array)
useEffect(() => {
console.log('Component mounted');
}, []);
// 3. Runs when userId changes
useEffect(() => {
console.log('Fetching user data for:', userId);
fetchUser(userId).then(setUser);
}, [userId]);
// 4. Runs when user changes
useEffect(() => {
if (user) {
console.log('Fetching posts for user:', user.id);
fetchUserPosts(user.id).then(setPosts);
}
}, [user]);
return <div>{/* component JSX */}</div>;
}
Execution Order: 1. Component renders 2. Effects are scheduled (not executed immediately) 3. Browser paints the screen 4. Effects execute in order they were defined
50. What are the differences between synchronous and asynchronous effects?
Answer:
Synchronous Effects:
useEffect(() => {
// Synchronous operations
document.title = `User: ${user.name}`;
localStorage.setItem('lastUser', user.id);
// Direct DOM manipulation
const element = document.getElementById('user-info');
if (element) {
element.style.color = 'blue';
}
}, [user]);
Asynchronous Effects:
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// Check if component is still mounted
if (isMounted) {
setUser(data);
}
} catch (error) {
if (isMounted) {
setError(error);
}
}
};
fetchData();
return () => {
isMounted = false; // Cleanup flag
};
}, [userId]);
Key Differences: - Synchronous: Execute immediately, block execution - Asynchronous: Use async/await, don't block, need cleanup - Synchronous: Good for DOM updates, localStorage - Asynchronous: Good for API calls, timers, subscriptions
51. Explain React performance optimization techniques
Prefer simple component structure and correct state placement first. Use stable keys, split code at useful boundaries, virtualize very large lists, and profile user interactions. React.memo, useMemo, and useCallback are optional performance tools, not correctness mechanisms. Reference: Memoization.
52. What are the differences between React.memo and useMemo?
React.memo is a performance optimization that can skip rendering when props compare equal (Object.is by default). State or consumed context can still trigger updates, and React may render anyway. useMemo caches a calculation result; useCallback caches a function identity. None guarantees correctness or should be required to keep an application working. Measure before adding memoization. Reference: memo.
53. Explain React's shouldComponentUpdate and React.memo
Answer:
Class Component - shouldComponentUpdate:
class UserProfile extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Only update if user ID or name changed
return (
this.props.userId !== nextProps.userId ||
this.props.userName !== nextProps.userName ||
this.state.isEditing !== nextState.isEditing
);
}
render() {
return <div>{/* component JSX */}</div>;
}
}
Functional Component - React.memo:
const UserProfile = React.memo(({ userId, userName, onEdit }) => {
const [isEditing, setIsEditing] = useState(false);
return (
<div>
<h2>{userName}</h2>
{isEditing ? (
<EditForm onSave={() => setIsEditing(false)} />
) : (
<button onClick={() => setIsEditing(true)}>Edit</button>
)}
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison function
return (
prevProps.userId === nextProps.userId &&
prevProps.userName === nextProps.userName
);
});
Custom Comparison Function:
const ExpensiveComponent = React.memo(
({ data, config }) => {
return <div>{/* expensive rendering */}</div>;
},
(prevProps, nextProps) => {
// Deep comparison for objects
return (
JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data) &&
prevProps.config.theme === nextProps.config.theme
);
}
);
54. What is the difference between shallow and deep comparison?
Answer:
Shallow Comparison:
// React's default comparison (shallow)
const UserList = React.memo(({ users, onUserClick }) => {
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} onClick={onUserClick} />
))}
</div>
);
});
// This will re-render even if user object properties haven't changed
function ParentComponent() {
const [users, setUsers] = useState([
{ id: 1, name: 'John', email: 'john@example.com' }
]);
const handleUserClick = (userId) => {
console.log('User clicked:', userId);
};
return <UserList users={users} onUserClick={handleUserClick} />;
}
Deep Comparison:
// Custom deep comparison
const UserList = React.memo(
({ users, onUserClick }) => {
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} onClick={onUserClick} />
))}
</div>
);
},
(prevProps, nextProps) => {
// Deep comparison of users array
if (prevProps.users.length !== nextProps.users.length) {
return false;
}
return prevProps.users.every((user, index) => {
const nextUser = nextProps.users[index];
return (
user.id === nextUser.id &&
user.name === nextUser.name &&
user.email === nextUser.email
);
});
}
);
Performance Impact:
// Shallow comparison - fast but may miss changes
const shallowEqual = (obj1, obj2) => {
return obj1 === obj2;
};
// Deep comparison - thorough but slower
const deepEqual = (obj1, obj2) => {
return JSON.stringify(obj1) === JSON.stringify(obj2);
};
// Optimized deep comparison
const optimizedDeepEqual = (obj1, obj2) => {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
if (obj1 === null || obj2 === null) return false;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(key => optimizedDeepEqual(obj1[key], obj2[key]));
};
55. Explain React's key prop and its importance
Answer:
Key Prop Importance:
// Without key - React can't track items properly
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li>{todo.text}</li> // ❌ No key - React can't optimize
))}
</ul>
);
}
// With key - React can efficiently update
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li> // ✅ With key - React can optimize
))}
</ul>
);
}
Key Scenarios:
// 1. Adding/removing items
function DynamicList({ items }) {
return (
<div>
{items.map((item, index) => (
<div key={item.id} className="item">
{item.name}
</div>
))}
</div>
);
}
// 2. Reordering items
function SortableList({ items, sortBy }) {
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a[sortBy].localeCompare(b[sortBy]));
}, [items, sortBy]);
return (
<div>
{sortedItems.map(item => (
<div key={item.id} className="item">
{item.name}
</div>
))}
</div>
);
}
// 3. Conditional rendering
function ConditionalList({ showItems, items }) {
return (
<div>
{showItems && items.map(item => (
<div key={item.id} className="item">
{item.name}
</div>
))}
</div>
);
}
Common Key Mistakes:
// ❌ Bad: Using index as key
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}
// ❌ Bad: Using random values
{todos.map(todo => (
<TodoItem key={Math.random()} todo={todo} />
))}
// ✅ Good: Using stable, unique IDs
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
// ✅ Good: Using stable composite keys
{users.map(user => (
<UserCard key={`${user.id}-${user.role}`} user={user} />
))}
56. What are the differences between stable and unstable keys?
Answer:
Stable Keys:
// Stable keys - same across renders
function UserList({ users }) {
return (
<div>
{users.map(user => (
<UserCard
key={user.id} // ✅ Stable - user.id doesn't change
user={user}
/>
))}
</div>
);
}
// Even with reordering, keys remain stable
function SortableUserList({ users }) {
const [sortBy, setSortBy] = useState('name');
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => a[sortBy].localeCompare(b[sortBy]));
}, [users, sortBy]);
return (
<div>
{sortedUsers.map(user => (
<UserCard
key={user.id} // ✅ Still stable after sorting
user={user}
/>
))}
</div>
);
}
Unstable Keys:
// Unstable keys - change on every render
function UnstableList({ items }) {
return (
<div>
{items.map((item, index) => (
<Item
key={Math.random()} // ❌ Unstable - changes every render
item={item}
/>
))}
</div>
);
}
// Index as key can be unstable when items change
function TodoList({ todos }) {
return (
<div>
{todos.map((todo, index) => (
<TodoItem
key={index} // ❌ Unstable when todos are added/removed
todo={todo}
/>
))}
</div>
);
}
Performance Impact:
// With stable keys - React can optimize efficiently
function OptimizedList({ items }) {
return (
<div>
{items.map(item => (
<ExpensiveComponent
key={item.id} // ✅ React can reuse components
data={item.data}
/>
))}
</div>
);
}
// With unstable keys - React recreates components
function UnoptimizedList({ items }) {
return (
<div>
{items.map((item, index) => (
<ExpensiveComponent
key={`${item.id}-${Date.now()}`} // ❌ React recreates every time
data={item.data}
/>
))}
</div>
);
}
57. Explain React's lazy loading and code splitting
Answer:
Basic Lazy Loading:
import React, { lazy, Suspense } from 'react';
// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<UserProfile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
}
Advanced Code Splitting:
// Route-based splitting
const routes = {
dashboard: lazy(() => import('./pages/Dashboard')),
profile: lazy(() => import('./pages/UserProfile')),
settings: lazy(() => import('./pages/Settings')),
analytics: lazy(() => import('./pages/Analytics')),
};
function App() {
const [currentRoute, setCurrentRoute] = useState('dashboard');
const CurrentComponent = routes[currentRoute];
return (
<div>
<Navigation onRouteChange={setCurrentRoute} />
<Suspense fallback={<LoadingSpinner />}>
<CurrentComponent />
</Suspense>
</div>
);
}
Conditional Lazy Loading:
// Load components based on user permissions
function ConditionalComponent({ userRole }) {
const AdminPanel = lazy(() => import('./AdminPanel'));
const UserPanel = lazy(() => import('./UserPanel'));
return (
<Suspense fallback={<div>Loading panel...</div>}>
{userRole === 'admin' ? <AdminPanel /> : <UserPanel />}
</Suspense>
);
}
// Load components based on feature flags
function FeatureComponent({ featureEnabled }) {
const [FeatureComponent, setFeatureComponent] = useState(null);
useEffect(() => {
if (featureEnabled) {
import('./FeatureComponent').then(module => {
setFeatureComponent(() => module.default);
});
}
}, [featureEnabled]);
if (!featureEnabled || !FeatureComponent) {
return <div>Feature not available</div>;
}
return (
<Suspense fallback={<div>Loading feature...</div>}>
<FeatureComponent />
</Suspense>
);
}
58. What is the difference between lazy loading and eager loading?
Answer:
Eager Loading (Default):
// All components loaded immediately
import Dashboard from './pages/Dashboard';
import UserProfile from './pages/UserProfile';
import Settings from './pages/Settings';
function App() {
return (
<Router>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<UserProfile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Router>
);
}
Lazy Loading:
// Components loaded on demand
const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<UserProfile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
}
Performance Comparison:
// Eager loading - larger initial bundle
// bundle.js: 2MB (includes all components)
// Lazy loading - smaller initial bundle
// bundle.js: 500KB (core components only)
// dashboard.chunk.js: 300KB (loaded when needed)
// profile.chunk.js: 250KB (loaded when needed)
// settings.chunk.js: 200KB (loaded when needed)
Use Cases:
// Eager loading for critical components
import Header from './components/Header';
import Footer from './components/Footer';
import Navigation from './components/Navigation';
// Lazy loading for non-critical components
const Modal = lazy(() => import('./components/Modal'));
const Tooltip = lazy(() => import('./components/Tooltip'));
const Chart = lazy(() => import('./components/Chart'));
function App() {
return (
<div>
<Header />
<Navigation />
<main>
<Suspense fallback={<div>Loading...</div>}>
<Modal />
</Suspense>
</main>
<Footer />
</div>
);
}
59. Explain React's bundle optimization
Answer:
Webpack Bundle Analysis:
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
}),
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true,
},
},
},
},
};
Tree Shaking:
// Good - tree shakeable
import { useState, useEffect } from 'react'; // ✅ Only imports what's used
// Bad - imports entire library
import * as React from 'react'; // ❌ Imports everything
// Good - named exports
export const Button = () => <button>Click me</button>;
export const Input = () => <input />;
// Bad - default export with side effects
export default {
Button: () => <button>Click me</button>,
Input: () => <input />,
};
Dynamic Imports:
// Dynamic imports for better code splitting
function DynamicComponent({ componentName }) {
const [Component, setComponent] = useState(null);
useEffect(() => {
import(`./components/${componentName}`)
.then(module => setComponent(() => module.default))
.catch(err => console.error('Failed to load component:', err));
}, [componentName]);
if (!Component) return <div>Loading...</div>;
return <Component />;
}
// Preloading components
function App() {
const preloadComponent = (componentName) => {
import(`./components/${componentName}`);
};
return (
<div>
<button onMouseEnter={() => preloadComponent('Modal')}>
Open Modal
</button>
</div>
);
}
60. What are the differences between development and production builds?
Answer:
Development Build:
// Development - verbose logging, warnings, debugging
function DevelopmentComponent() {
console.log('Component rendered'); // ✅ Shown in dev
console.warn('Deprecated feature used'); // ✅ Shown in dev
return (
<div>
<h1>Development Mode</h1>
{/* React DevTools work */}
{/* Source maps available */}
{/* Hot reloading enabled */}
</div>
);
}
Production Build:
// Production - optimized, minified, no debugging
function ProductionComponent() {
// console.log statements are stripped
// console.warn statements are stripped
return (
<div>
<h1>Production Mode</h1>
{/* No DevTools */}
{/* No source maps (unless configured) */}
{/* No hot reloading */}
</div>
);
}
Build Configuration:
// webpack.config.js
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map',
optimization: {
minimize: isProduction,
splitChunks: isProduction ? {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
} : false,
},
plugins: [
isProduction && new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // Remove console.log
},
},
}),
].filter(Boolean),
};
};
Environment Variables:
// .env.development
REACT_APP_API_URL=http://localhost:3000/api
REACT_APP_DEBUG=true
REACT_APP_LOG_LEVEL=debug
// .env.production
REACT_APP_API_URL=https://api.production.com
REACT_APP_DEBUG=false
REACT_APP_LOG_LEVEL=error
// Usage in code
function App() {
const apiUrl = process.env.REACT_APP_API_URL;
const isDebug = process.env.REACT_APP_DEBUG === 'true';
if (isDebug) {
console.log('API URL:', apiUrl);
}
return <div>App</div>;
}
Performance Differences:
// Development - slower, larger bundle
// - Source maps included
// - React DevTools enabled
// - Hot reloading overhead
// - Unminified code
// - Development warnings
// Production - faster, smaller bundle
// - Minified code
// - Tree shaking applied
// - Dead code elimination
// - Optimized bundle splitting
// - No development overhead
61. Explain React Router and its configuration
Answer: React Router is a powerful routing library for React applications that enables client-side routing, allowing you to create single-page applications (SPAs) with multiple views without page refreshes.
Key Concepts: - Routing: Maps URLs to components - Navigation: Programmatic and declarative navigation - History Management: Browser history API integration - Nested Routes: Hierarchical routing structure
Basic Configuration Example:
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import UserProfile from './pages/UserProfile';
import Layout from './components/Layout';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="user/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Advanced Configuration with Lazy Loading:
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
62. What are the differences between BrowserRouter and HashRouter?
Answer:
| Aspect | BrowserRouter | HashRouter |
|---|---|---|
| URL Structure | example.com/about |
example.com/#/about |
| Server Requirements | Requires server configuration for SPA | Works with any server |
| SEO | Better for SEO | Poor SEO due to hash |
| Browser History | Uses HTML5 History API | Uses hash-based routing |
| Server-Side Rendering | Compatible with SSR | Limited SSR support |
| Deployment | Requires server routing configuration | Works out of the box |
BrowserRouter Example:
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
{/* Routes */}
</BrowserRouter>
);
}
HashRouter Example:
import { HashRouter } from 'react-router-dom';
function App() {
return (
<HashRouter>
{/* Routes */}
</HashRouter>
);
}
Server Configuration for BrowserRouter (Apache):
# .htaccess
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
63. Explain React Router hooks and their usage
Answer: React Router provides several hooks for accessing routing information and navigation.
1. useNavigate - Programmatic Navigation:
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleLogin = async (credentials) => {
try {
await loginUser(credentials);
navigate('/dashboard'); // Navigate to dashboard
// or navigate('/dashboard', { replace: true }); // Replace current entry
} catch (error) {
navigate('/login', { state: { error: 'Login failed' } });
}
};
return (
<form onSubmit={handleLogin}>
{/* form fields */}
</form>
);
}
2. useParams - Access Route Parameters:
import { useParams } from 'react-router-dom';
function UserProfile() {
const { id, tab } = useParams(); // From route: /user/:id/:tab?
useEffect(() => {
fetchUserData(id);
}, [id]);
return (
<div>
<h1>User Profile: {id}</h1>
{tab && <p>Active Tab: {tab}</p>}
</div>
);
}
3. useSearchParams - Query String Management:
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const page = searchParams.get('page') || '1';
const updateFilters = (newCategory) => {
setSearchParams({
category: newCategory,
page: '1'
});
};
return (
<div>
<button onClick={() => updateFilters('electronics')}>
Electronics
</button>
<p>Category: {category}</p>
<p>Page: {page}</p>
</div>
);
}
4. useLocation - Access Current Location:
import { useLocation } from 'react-router-dom';
function Analytics() {
const location = useLocation();
useEffect(() => {
// Track page view
analytics.track('page_view', {
path: location.pathname,
search: location.search,
state: location.state
});
}, [location]);
return <div>Current path: {location.pathname}</div>;
}
5. useOutlet - Access Nested Route Content:
import { useOutlet } from 'react-router-dom';
function Layout() {
const outlet = useOutlet();
return (
<div className="layout">
<header>App Header</header>
<main>{outlet}</main>
<footer>App Footer</footer>
</div>
);
}
64. What is the difference between useParams and useSearchParams?
Answer:
| Aspect | useParams | useSearchParams |
|---|---|---|
| Data Source | Route path parameters | URL query string |
| URL Structure | /user/123/profile |
/user/123?tab=profile&sort=name |
| Access Pattern | Direct object access | Getter/setter methods |
| Mutability | Read-only | Read-write |
| Use Case | Required route data | Optional filters/state |
useParams Example:
// Route: /product/:category/:id
function ProductDetail() {
const { category, id } = useParams();
// category = "electronics", id = "123"
return (
<div>
<h1>Product {id} in {category}</h1>
</div>
);
}
useSearchParams Example:
// URL: /products?category=electronics&sort=price&page=2
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const sort = searchParams.get('sort');
const page = searchParams.get('page');
const updateSort = (newSort) => {
setSearchParams(prev => {
prev.set('sort', newSort);
return prev;
});
};
return (
<div>
<select onChange={(e) => updateSort(e.target.value)} value={sort}>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
</div>
);
}
65. Explain route guards and protected routes
A protected route changes client navigation, not server authorization. Render a loading/unauthenticated state while identity is being established, redirect or render an access-denied page as appropriate, and enforce every sensitive API operation on the server. Authorize every operation on the server; a hidden button or protected client route is only UI behavior. Client bundles and client environment variables are public. Prefer established authentication flows and carefully chosen session storage. HttpOnly, Secure cookies limit JavaScript token access but require CSRF defenses and appropriate SameSite settings. Tokens in localStorage are exposed to script running in the origin. Escape text, sanitize any intentionally rendered HTML, and apply an appropriate content-security policy. Hashing or encrypting a token with a secret shipped in the same bundle does not make it secret.
66. What are the differences between route guards and authentication?
Answer:
| Aspect | Route Guards | Authentication |
|---|---|---|
| Purpose | Route protection mechanism | User identity verification |
| Scope | Route-level access control | Application-wide user state |
| Implementation | React components/hooks | Service/context providers |
| Timing | Before route rendering | Continuous throughout app |
| Responsibility | Access control logic | User session management |
Authentication Service:
// auth/useAuth.ts
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
refreshToken: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const login = async (credentials: LoginCredentials) => {
try {
const response = await authService.login(credentials);
setUser(response.user);
localStorage.setItem('token', response.token);
} catch (error) {
throw new Error('Login failed');
}
};
const logout = () => {
setUser(null);
localStorage.removeItem('token');
};
const refreshToken = async () => {
try {
const token = localStorage.getItem('token');
if (token) {
const user = await authService.validateToken(token);
setUser(user);
}
} catch (error) {
logout();
} finally {
setIsLoading(false);
}
};
useEffect(() => {
refreshToken();
}, []);
return (
<AuthContext.Provider value={{ user, isAuthenticated: !!user, login, logout, refreshToken }}>
{children}
</AuthContext.Provider>
);
}
Route Guard Using Authentication:
function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
const { isAuthenticated, user } = useAuth();
const location = useLocation();
// Route guard logic using authentication state
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (requiredRole && user?.role !== requiredRole) {
return <Navigate to="/unauthorized" replace />;
}
return <>{children}</>;
}
67. Explain nested routes and their implementation
Answer: Nested routes allow you to create hierarchical routing structures where child routes render within parent route layouts.
Basic Nested Routes:
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<DashboardOverview />} />
<Route path="profile" element={<UserProfile />} />
<Route path="settings" element={<Settings />}>
<Route path="account" element={<AccountSettings />} />
<Route path="privacy" element={<PrivacySettings />} />
</Route>
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
Layout Component with Outlet:
function Layout() {
return (
<div className="app-layout">
<Header />
<Sidebar />
<main className="main-content">
<Outlet /> {/* Child routes render here */}
</main>
<Footer />
</div>
);
}
function Dashboard() {
return (
<div className="dashboard-layout">
<DashboardHeader />
<div className="dashboard-content">
<Outlet /> {/* Dashboard child routes render here */}
</div>
</div>
);
}
Dynamic Nested Routes:
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="users" element={<UserLayout />}>
<Route index element={<UserList />} />
<Route path=":userId" element={<UserDetail />}>
<Route index element={<UserOverview />} />
<Route path="posts" element={<UserPosts />} />
<Route path="posts/:postId" element={<PostDetail />} />
</Route>
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
Nested Routes with Breadcrumbs:
function Dashboard() {
const location = useLocation();
const navigate = useNavigate();
const breadcrumbs = [
{ path: '/dashboard', label: 'Dashboard' },
{ path: '/dashboard/profile', label: 'Profile' },
{ path: '/dashboard/settings', label: 'Settings' }
];
return (
<div className="dashboard">
<Breadcrumb items={breadcrumbs} />
<div className="dashboard-content">
<Outlet />
</div>
</div>
);
}
68. What is the difference between nested routes and route composition?
Answer:
| Aspect | Nested Routes | Route Composition |
|---|---|---|
| Structure | Hierarchical parent-child | Flat route structure |
| Layout | Shared parent layouts | Independent layouts |
| URL Structure | /parent/child |
/parent and /child |
| State Sharing | Shared context/state | Isolated state |
| Navigation | Relative navigation | Absolute navigation |
Nested Routes Example:
// Hierarchical structure
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<Overview />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
// URLs: /dashboard, /dashboard/profile, /dashboard/settings
Route Composition Example:
// Flat structure with shared components
function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dashboard-layout">
<Sidebar />
<main>{children}</main>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={
<DashboardLayout>
<Overview />
</DashboardLayout>
} />
<Route path="/profile" element={
<DashboardLayout>
<Profile />
</DashboardLayout>
} />
<Route path="/settings" element={
<DashboardLayout>
<Settings />
</DashboardLayout>
} />
</Routes>
</BrowserRouter>
);
}
Advanced Route Composition with HOCs:
const withDashboardLayout = (Component: React.ComponentType) => {
return function WrappedComponent(props: any) {
return (
<DashboardLayout>
<Component {...props} />
</DashboardLayout>
);
};
};
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={withDashboardLayout(Overview)()} />
<Route path="/profile" element={withDashboardLayout(Profile)()} />
<Route path="/settings" element={withDashboardLayout(Settings)()} />
</Routes>
</BrowserRouter>
);
}
69. Explain React Router's history object
Answer: The history object manages browser history and provides methods for programmatic navigation.
History Object Properties:
import { useNavigate, useLocation } from 'react-router-dom';
function NavigationExample() {
const navigate = useNavigate();
const location = useLocation();
// History object properties
console.log(location.pathname); // Current path
console.log(location.search); // Query string
console.log(location.hash); // Hash fragment
console.log(location.state); // State passed via navigation
const handleNavigation = () => {
// Navigate with state
navigate('/dashboard', {
state: { from: 'home' },
replace: false // false = push, true = replace
});
};
return (
<div>
<button onClick={handleNavigation}>Go to Dashboard</button>
</div>
);
}
Custom History Object:
import { createBrowserHistory } from 'history';
const customHistory = createBrowserHistory({
basename: '/app', // Base URL
forceRefresh: false, // Force page refresh
keyLength: 6 // Length of location key
});
function App() {
return (
<Router history={customHistory}>
<Routes>
{/* Routes */}
</Routes>
</Router>
);
}
History Listeners:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function AnalyticsTracker() {
const location = useLocation();
useEffect(() => {
// Track page views
analytics.track('page_view', {
path: location.pathname,
timestamp: new Date().toISOString()
});
}, [location]);
return null;
}
70. What are the differences between history and location objects?
Answer:
| Aspect | History Object | Location Object |
|---|---|---|
| Purpose | Navigation control | Current location info |
| Mutability | Mutable (can change) | Immutable (read-only) |
| Methods | push, replace, go, back | None (data only) |
| Data | Navigation methods | Current URL data |
| Usage | Programmatic navigation | Reading current state |
History Object Usage:
import { useNavigate } from 'react-router-dom';
function NavigationComponent() {
const navigate = useNavigate();
const handleNavigation = () => {
// History methods
navigate('/dashboard'); // push
navigate('/dashboard', { replace: true }); // replace
navigate(-1); // go back
navigate(2); // go forward
};
return (
<div>
<button onClick={() => navigate('/dashboard')}>Dashboard</button>
<button onClick={() => navigate(-1)}>Back</button>
</div>
);
}
Location Object Usage:
import { useLocation } from 'react-router-dom';
function LocationAwareComponent() {
const location = useLocation();
// Location properties
const {
pathname, // "/dashboard/profile"
search, // "?tab=settings"
hash, // "#section1"
state, // { from: "home" }
key // Unique key for this location
} = location;
return (
<div>
<p>Current path: {pathname}</p>
<p>Query string: {search}</p>
<p>Hash: {hash}</p>
<p>State: {JSON.stringify(state)}</p>
</div>
);
}
71. Explain React form handling and controlled components
Answer: React form handling involves managing form state and user input through controlled components, where React state is the "single source of truth."
Basic Controlled Form:
import { useState, FormEvent } from 'react';
interface FormData {
username: string;
email: string;
password: string;
}
function ControlledForm() {
const [formData, setFormData] = useState<FormData>({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState<Partial<FormData>>({});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
// Clear error when user starts typing
if (errors[name as keyof FormData]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
// Validation
const newErrors: Partial<FormData> = {};
if (!formData.username) newErrors.username = 'Username is required';
if (!formData.email) newErrors.email = 'Email is required';
if (!formData.password) newErrors.password = 'Password is required';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
try {
await submitForm(formData);
// Handle success
} catch (error) {
// Handle error
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
className={errors.username ? 'error' : ''}
/>
{errors.username && <span className="error-text">{errors.username}</span>}
</div>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={errors.email ? 'error' : ''}
/>
{errors.email && <span className="error-text">{errors.email}</span>}
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
className={errors.password ? 'error' : ''}
/>
{errors.password && <span className="error-text">{errors.password}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
Advanced Form with Custom Hook:
// hooks/useForm.ts
interface UseFormOptions<T> {
initialValues: T;
validationSchema?: ValidationSchema<T>;
onSubmit: (values: T) => Promise<void>;
}
function useForm<T extends Record<string, any>>({
initialValues,
validationSchema,
onSubmit
}: UseFormOptions<T>) {
const [values, setValues] = useState<T>(initialValues);
const [errors, setErrors] = useState<Partial<T>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (name: keyof T, value: any) => {
setValues(prev => ({ ...prev, [name]: value }));
// Clear error for this field
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
};
const validate = (): boolean => {
if (!validationSchema) return true;
try {
validationSchema.validateSync(values, { abortEarly: false });
setErrors({});
return true;
} catch (validationErrors: any) {
const newErrors: Partial<T> = {};
validationErrors.inner.forEach((error: any) => {
newErrors[error.path as keyof T] = error.message;
});
setErrors(newErrors);
return false;
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validate()) return;
setIsSubmitting(true);
try {
await onSubmit(values);
} catch (error) {
console.error('Form submission error:', error);
} finally {
setIsSubmitting(false);
}
};
const reset = () => {
setValues(initialValues);
setErrors({});
};
return {
values,
errors,
isSubmitting,
handleChange,
handleSubmit,
reset
};
}
72. What are the differences between controlled and uncontrolled forms?
Answer:
| Aspect | Controlled Forms | Uncontrolled Forms |
|---|---|---|
| State Management | React state | DOM state |
| Data Access | Immediate access | Ref-based access |
| Validation | Real-time validation | On-submit validation |
| Performance | Re-renders on every change | No re-renders |
| Complexity | More code, more control | Less code, less control |
Controlled Form Example:
function ControlledForm() {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log({ username, email }); // Immediate access
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}
Uncontrolled Form Example:
function UncontrolledForm() {
const usernameRef = useRef<HTMLInputElement>(null);
const emailRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const username = usernameRef.current?.value;
const email = emailRef.current?.value;
console.log({ username, email }); // Access via refs
};
return (
<form onSubmit={handleSubmit}>
<input
ref={usernameRef}
type="text"
placeholder="Username"
defaultValue=""
/>
<input
ref={emailRef}
type="email"
placeholder="Email"
defaultValue=""
/>
<button type="submit">Submit</button>
</form>
);
}
Hybrid Approach:
function HybridForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
const formRef = useRef<HTMLFormElement>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(formRef.current!);
const username = formData.get('username') as string;
const email = formData.get('email') as string;
// Validation
const newErrors: Record<string, string> = {};
if (!username) newErrors.username = 'Username required';
if (!email) newErrors.email = 'Email required';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
// Submit form
console.log({ username, email });
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input
name="username"
type="text"
placeholder="Username"
/>
{errors.username && <span>{errors.username}</span>}
<input
name="email"
type="email"
placeholder="Email"
/>
{errors.email && <span>{errors.email}</span>}
<button type="submit">Submit</button>
</form>
);
}
73. Explain form validation in React
Answer: Form validation ensures data integrity and provides user feedback. React offers multiple approaches for form validation.
Client-Side Validation with Custom Logic:
interface ValidationRules {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
custom?: (value: string) => string | null;
}
function useValidation() {
const validateField = (value: string, rules: ValidationRules): string | null => {
if (rules.required && !value.trim()) {
return 'This field is required';
}
if (rules.minLength && value.length < rules.minLength) {
return `Minimum length is ${rules.minLength} characters`;
}
if (rules.maxLength && value.length > rules.maxLength) {
return `Maximum length is ${rules.maxLength} characters`;
}
if (rules.pattern && !rules.pattern.test(value)) {
return 'Invalid format';
}
if (rules.custom) {
return rules.custom(value);
}
return null;
};
return { validateField };
}
function ValidatedForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState<Record<string, string>>({});
const { validateField } = useValidation();
const validationRules = {
username: {
required: true,
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/
},
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
password: {
required: true,
minLength: 8,
custom: (value: string) => {
if (!/(?=.*[a-z])/.test(value)) return 'Must contain lowercase letter';
if (!/(?=.*[A-Z])/.test(value)) return 'Must contain uppercase letter';
if (!/(?=.*\d)/.test(value)) return 'Must contain number';
return null;
}
},
confirmPassword: {
required: true,
custom: (value: string) => {
return value !== formData.password ? 'Passwords do not match' : null;
}
}
};
const handleChange = (name: string, value: string) => {
setFormData(prev => ({ ...prev, [name]: value }));
// Real-time validation
const error = validateField(value, validationRules[name as keyof typeof validationRules]);
setErrors(prev => ({
...prev,
[name]: error || ''
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Validate all fields
const newErrors: Record<string, string> = {};
Object.keys(formData).forEach(field => {
const error = validateField(
formData[field as keyof typeof formData],
validationRules[field as keyof typeof validationRules]
);
if (error) newErrors[field] = error;
});
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
// Submit form
console.log('Form is valid:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
value={formData.username}
onChange={(e) => handleChange('username', e.target.value)}
placeholder="Username"
className={errors.username ? 'error' : ''}
/>
{errors.username && <span className="error">{errors.username}</span>}
</div>
<div>
<input
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
placeholder="Email"
className={errors.email ? 'error' : ''}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input
type="password"
value={formData.password}
onChange={(e) => handleChange('password', e.target.value)}
placeholder="Password"
className={errors.password ? 'error' : ''}
/>
{errors.password && <span className="error">{errors.password}</span>}
</div>
<div>
<input
type="password"
value={formData.confirmPassword}
onChange={(e) => handleChange('confirmPassword', e.target.value)}
placeholder="Confirm Password"
className={errors.confirmPassword ? 'error' : ''}
/>
{errors.confirmPassword && <span className="error">{errors.confirmPassword}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
Validation with Yup Schema:
import * as yup from 'yup';
const validationSchema = yup.object({
username: yup
.string()
.required('Username is required')
.min(3, 'Username must be at least 3 characters')
.max(20, 'Username must be at most 20 characters')
.matches(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores'),
email: yup
.string()
.required('Email is required')
.email('Invalid email format'),
password: yup
.string()
.required('Password is required')
.min(8, 'Password must be at least 8 characters')
.matches(/(?=.*[a-z])/, 'Password must contain at least one lowercase letter')
.matches(/(?=.*[A-Z])/, 'Password must contain at least one uppercase letter')
.matches(/(?=.*\d)/, 'Password must contain at least one number'),
confirmPassword: yup
.string()
.required('Please confirm your password')
.oneOf([yup.ref('password')], 'Passwords must match')
});
function YupValidatedForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState<Record<string, string>>({});
const handleChange = (name: string, value: string) => {
setFormData(prev => ({ ...prev, [name]: value }));
// Validate single field
validationSchema.validateAt(name, { [name]: value })
.then(() => {
setErrors(prev => ({ ...prev, [name]: '' }));
})
.catch((error) => {
setErrors(prev => ({ ...prev, [name]: error.message }));
});
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await validationSchema.validate(formData, { abortEarly: false });
console.log('Form is valid:', formData);
} catch (validationErrors: any) {
const newErrors: Record<string, string> = {};
validationErrors.inner.forEach((error: any) => {
newErrors[error.path] = error.message;
});
setErrors(newErrors);
}
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields with validation */}
</form>
);
}
74. What is the difference between client-side and server-side validation?
Client-side validation occurs in the user's browser before data is sent to the server, while server-side validation happens on the server after data is received.
Client-side Validation
- Purpose: Immediate feedback, better UX, reduces server load
- Limitations: Can be bypassed, not secure for critical validation
- Tools: HTML5 attributes, JavaScript, React form libraries
Server-side Validation
- Purpose: Security, data integrity, business logic enforcement
- Advantages: Cannot be bypassed, handles complex validation rules
- Tools: Backend frameworks, database constraints
SQL Example - Server-side Validation
-- Database constraint for email validation
CREATE TABLE users (
id INT PRIMARY KEY IDENTITY(1,1),
email VARCHAR(255) NOT NULL,
CONSTRAINT chk_email_format CHECK (email LIKE '%_@__%.__%'),
CONSTRAINT chk_email_unique UNIQUE (email)
);
-- Stored procedure with validation
CREATE PROCEDURE CreateUser
@Email VARCHAR(255),
@Password VARCHAR(255)
AS
BEGIN
-- Server-side validation
IF @Email IS NULL OR LEN(@Email) = 0
THROW 50001, 'Email is required', 1;
IF @Email NOT LIKE '%_@__%.__%'
THROW 50002, 'Invalid email format', 1;
IF EXISTS (SELECT 1 FROM users WHERE email = @Email)
THROW 50003, 'Email already exists', 1;
-- Insert if validation passes
INSERT INTO users (email, password_hash)
VALUES (@Email, HASHBYTES('SHA2_256', @Password));
END;
React Example - Client-side Validation
// Custom hook for form validation
const useFormValidation = (initialValues: any, validationSchema: any) => {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validate = (fieldValues = values) => {
const tempErrors = {};
Object.keys(validationSchema).forEach(key => {
const value = fieldValues[key];
const rules = validationSchema[key];
if (rules.required && !value) {
tempErrors[key] = `${key} is required`;
} else if (rules.pattern && !rules.pattern.test(value)) {
tempErrors[key] = rules.message;
}
});
setErrors(tempErrors);
return Object.keys(tempErrors).length === 0;
};
return { values, errors, touched, validate, setValues, setTouched };
};
75. Explain React form libraries (Formik, React Hook Form)
Formik
Formik is a popular form library that handles form state, validation, and submission.
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const validationSchema = Yup.object({
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(8, 'Must be 8 characters').required('Required'),
});
const LoginForm = () => (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
// Handle form submission
console.log(values);
setSubmitting(false);
}}
>
{({ isSubmitting }) => (
<Form>
<Field name="email" type="email" placeholder="Email" />
<ErrorMessage name="email" component="div" />
<Field name="password" type="password" placeholder="Password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
);
React Hook Form
React Hook Form is a performant form library that minimizes re-renders.
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required(),
});
const LoginForm = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset
} = useForm({
resolver: yupResolver(schema)
});
const onSubmit = async (data: any) => {
try {
// Handle form submission
console.log(data);
reset();
} catch (error) {
console.error('Submission error:', error);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('email')}
type="email"
placeholder="Email"
/>
{errors.email && <span>{errors.email.message}</span>}
<input
{...register('password')}
type="password"
placeholder="Password"
/>
{errors.password && <span>{errors.password.message}</span>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
);
};
76. What are the differences between Formik and React Hook Form?
| Aspect | Formik | React Hook Form |
|---|---|---|
| Performance | Re-renders on every keystroke | Minimal re-renders |
| Bundle Size | Larger (~14KB) | Smaller (~8KB) |
| Learning Curve | Easier for beginners | Steeper learning curve |
| Validation | Built-in with Yup integration | Flexible validation options |
| TypeScript | Good support | Excellent support |
| API Design | Render props pattern | Hook-based |
Performance Comparison Example
// Formik - re-renders on every change
const FormikExample = () => (
<Formik initialValues={{ name: '' }}>
{({ values, setFieldValue }) => (
<input
value={values.name}
onChange={(e) => setFieldValue('name', e.target.value)}
/>
)}
</Formik>
);
// React Hook Form - controlled re-renders
const HookFormExample = () => {
const { register } = useForm();
return (
<input {...register('name')} />
);
};
77. Explain form submission and error handling
Form Submission Patterns
// Async form submission with error handling
const useFormSubmission = () => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const submitForm = async (formData: any) => {
setIsSubmitting(true);
setError(null);
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Submission failed');
}
const result = await response.json();
setSuccess(true);
return result;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
} finally {
setIsSubmitting(false);
}
};
return { submitForm, isSubmitting, error, success };
};
Error Handling Strategies
// Comprehensive error handling component
const FormWithErrorHandling = () => {
const { submitForm, isSubmitting, error, success } = useFormSubmission();
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data: any) => {
try {
await submitForm(data);
} catch (err) {
// Error already handled in useFormSubmission
console.error('Form submission failed:', err);
}
};
return (
<div>
{error && (
<div className="error-banner">
<Alert severity="error">{error}</Alert>
</div>
)}
{success && (
<div className="success-banner">
<Alert severity="success">Form submitted successfully!</Alert>
</div>
)}
<form onSubmit={handleSubmit(onSubmit)}>
{/* Form fields */}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
</div>
);
};
78. What is the difference between form submission and form validation?
Form Validation ensures data meets requirements before submission, while Form Submission sends validated data to the server.
Validation vs Submission Example
// Validation layer
const validateForm = (data: any) => {
const errors: Record<string, string> = {};
// Business logic validation
if (!data.email) {
errors.email = 'Email is required';
} else if (!isValidEmail(data.email)) {
errors.email = 'Invalid email format';
}
if (!data.password) {
errors.password = 'Password is required';
} else if (data.password.length < 8) {
errors.password = 'Password must be at least 8 characters';
}
return { isValid: Object.keys(errors).length === 0, errors };
};
// Submission layer
const submitForm = async (data: any) => {
// Validation happens before submission
const validation = validateForm(data);
if (!validation.isValid) {
throw new Error('Form validation failed');
}
// Proceed with submission
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
};
79. Explain dynamic forms and their implementation
Dynamic forms are forms whose structure and fields change based on data, user input, or business rules.
Dynamic Form Implementation
// Dynamic form configuration
interface FormField {
id: string;
type: 'text' | 'email' | 'select' | 'checkbox' | 'conditional';
label: string;
required?: boolean;
options?: { value: string; label: string }[];
dependsOn?: string;
condition?: (values: any) => boolean;
}
// Dynamic form component
const DynamicForm = ({ config, onSubmit }: { config: FormField[], onSubmit: (data: any) => void }) => {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const watchedValues = watch();
const shouldShowField = (field: FormField) => {
if (!field.dependsOn || !field.condition) return true;
return field.condition(watchedValues);
};
const renderField = (field: FormField) => {
if (!shouldShowField(field)) return null;
switch (field.type) {
case 'text':
case 'email':
return (
<div key={field.id}>
<label>{field.label}</label>
<input
{...register(field.id, { required: field.required })}
type={field.type}
/>
{errors[field.id] && <span>{errors[field.id].message}</span>}
</div>
);
case 'select':
return (
<div key={field.id}>
<label>{field.label}</label>
<select {...register(field.id, { required: field.required })}>
<option value="">Select...</option>
{field.options?.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
);
case 'conditional':
return (
<div key={field.id}>
<label>{field.label}</label>
<input
{...register(field.id)}
type="text"
disabled={!shouldShowField(field)}
/>
</div>
);
default:
return null;
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{config.map(renderField)}
<button type="submit">Submit</button>
</form>
);
};
// Usage example
const formConfig: FormField[] = [
{
id: 'userType',
type: 'select',
label: 'User Type',
required: true,
options: [
{ value: 'individual', label: 'Individual' },
{ value: 'business', label: 'Business' }
]
},
{
id: 'companyName',
type: 'text',
label: 'Company Name',
dependsOn: 'userType',
condition: (values) => values.userType === 'business'
},
{
id: 'email',
type: 'email',
label: 'Email',
required: true
}
];
80. What are the differences between static and dynamic forms?
| Aspect | Static Forms | Dynamic Forms |
|---|---|---|
| Structure | Fixed fields and layout | Configurable fields and layout |
| Flexibility | Limited | High |
| Maintenance | Requires code changes | Configuration-driven |
| Performance | Optimized for specific use case | May have overhead |
| Complexity | Simple to implement | More complex architecture |
Static Form Example
const StaticUserForm = () => {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('firstName')} placeholder="First Name" />
<input {...register('lastName')} placeholder="Last Name" />
<input {...register('email')} type="email" placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
};
Dynamic Form Example
const DynamicFormRenderer = ({ schema }: { schema: FormSchema }) => {
const [formConfig, setFormConfig] = useState(schema);
// Form can be updated dynamically
const updateFormConfig = (newConfig: FormSchema) => {
setFormConfig(newConfig);
};
return (
<DynamicForm
config={formConfig.fields}
onSubmit={formConfig.onSubmit}
/>
);
};
81. Explain React testing frameworks and tools
Testing Ecosystem
- Jest: Test runner and assertion library
- React Testing Library: Component testing utilities
- Cypress: E2E testing
- Playwright: Cross-browser testing
- MSW: API mocking
Testing Setup Example
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapping: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'^@/(.*)$': '<rootDir>/src/$1'
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx'
]
};
// setupTests.ts
import '@testing-library/jest-dom';
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
82. What are the differences between Jest and Mocha?
| Feature | Jest | Mocha |
|---|---|---|
| Test Runner | Built-in | Requires separate runner |
| Assertions | Built-in expect() | Requires Chai/Sinon |
| Mocking | Built-in jest.fn() | Requires Sinon |
| Coverage | Built-in | Requires Istanbul |
| Configuration | Minimal setup | More configuration needed |
| Performance | Faster | Slower |
| Snapshot Testing | Built-in | Not available |
Jest Example
// Component test with Jest
import { render, screen, fireEvent } from '@testing-library/react';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
test('submits form with valid data', async () => {
const mockSubmit = jest.fn();
render(<LoginForm onSubmit={mockSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' }
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
});
Mocha Example
// Component test with Mocha
import { expect } from 'chai';
import { render, screen, fireEvent } from '@testing-library/react';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('should submit form with valid data', async () => {
const mockSubmit = sinon.spy();
render(<LoginForm onSubmit={mockSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
});
83. Explain React Testing Library and its philosophy
Use a maintained test runner and React Testing Library to test observable behavior. Avoid tests that only assert component implementation details. Mock network boundaries deliberately, test loading/error/success states, and use accessible queries wherever possible.
84. What is the difference between Testing Library and Enzyme?
| Aspect | React Testing Library | Enzyme |
|---|---|---|
| Philosophy | Test behavior | Test implementation |
| Queries | Accessible queries | CSS selectors |
| Encourages | User-centric testing | Implementation testing |
| Bundle Size | Smaller | Larger |
| Learning Curve | Steeper | Easier |
| Future | Recommended by React team | Legacy |
Testing Library Example
import { render, screen, fireEvent } from '@testing-library/react';
import { LoginForm } from './LoginForm';
test('user can login', async () => {
render(<LoginForm />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /sign in/i }));
expect(screen.getByText(/welcome/i)).toBeInTheDocument();
});
Enzyme Example
import { shallow, mount } from 'enzyme';
import { LoginForm } from './LoginForm';
test('form submission', () => {
const wrapper = shallow(<LoginForm />);
// Testing implementation details
wrapper.find('input[name="email"]').simulate('change', {
target: { value: 'test@example.com' }
});
wrapper.find('form').simulate('submit');
expect(wrapper.state('email')).toBe('test@example.com');
});
85. Explain component testing and rendering
Component Testing Strategies
// Unit testing individual components
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
describe('Button Component', () => {
test('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
test('handles click events', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('applies disabled state', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
Integration Testing
// Testing component interactions
import { render, screen, waitFor } from '@testing-library/react';
import { LoginForm } from './LoginForm';
import { AuthProvider } from './AuthContext';
test('login flow integration', async () => {
render(
<AuthProvider>
<LoginForm />
</AuthProvider>
);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
});
});
86. What are the differences between render and mount?
| Aspect | render | mount |
|---|---|---|
| Depth | Shallow rendering | Full DOM rendering |
| Performance | Faster | Slower |
| Use Case | Unit testing | Integration testing |
| Dependencies | Minimal | Full component tree |
| Lifecycle | Limited | Full lifecycle methods |
Render Example (Shallow)
import { render } from '@testing-library/react';
test('shallow render test', () => {
const { container } = render(<UserProfile />);
// Only tests the immediate component
expect(container.firstChild).toMatchSnapshot();
});
Mount Example (Full DOM)
import { render } from '@testing-library/react';
test('full mount test', () => {
const { container } = render(
<UserProfile />,
{ wrapper: BrowserRouter }
);
// Tests entire component tree including child components
expect(container).toMatchSnapshot();
});
87. Explain mocking in React tests
Mocking Strategies
// Mocking API calls
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
])
);
})
);
// Mocking components
jest.mock('./ExpensiveComponent', () => {
return function MockExpensiveComponent() {
return <div data-testid="mock-expensive">Mock Component</div>;
};
});
// Mocking hooks
const mockUseAuth = jest.fn();
jest.mock('./hooks/useAuth', () => ({
useAuth: () => mockUseAuth()
}));
// Mocking context
const MockAuthContext = ({ children }: { children: React.ReactNode }) => (
<AuthContext.Provider value={{
user: { id: 1, name: 'Test User' },
login: jest.fn(),
logout: jest.fn()
}}>
{children}
</AuthContext.Provider>
);
Testing with Mocks
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';
describe('UserList', () => {
beforeEach(() => {
mockUseAuth.mockReturnValue({
user: { id: 1, name: 'Test User' },
isAuthenticated: true
});
});
test('renders user list', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});
});
88. What is the difference between mocking and stubbing?
| Aspect | Mocking | Stubbing |
|---|---|---|
| Purpose | Verify behavior | Replace functionality |
| Verification | Checks if methods were called | No verification |
| Complexity | More complex | Simpler |
| Use Case | Behavior testing | Data replacement |
Mocking Example
// Mocking - verifies behavior
test('calls API on form submission', async () => {
const mockApiCall = jest.fn().mockResolvedValue({ success: true });
render(<LoginForm onSubmit={mockApiCall} />);
await user.click(screen.getByRole('button', { name: /submit/i }));
// Verifies the function was called
expect(mockApiCall).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
Stubbing Example
// Stubbing - replaces functionality
test('displays user data', () => {
// Stub the API response
const userData = { name: 'John Doe', email: 'john@example.com' };
jest.spyOn(api, 'getUser').mockResolvedValue(userData);
render(<UserProfile />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
89. Explain snapshot testing and its benefits
Snapshot testing captures a component's rendered output and compares it against future changes.
Snapshot Testing Example
import { render } from '@testing-library/react';
import { Button } from './Button';
describe('Button Component', () => {
test('matches snapshot', () => {
const { container } = render(<Button>Click me</Button>);
expect(container.firstChild).toMatchSnapshot();
});
test('matches snapshot with different props', () => {
const { container } = render(
<Button variant="primary" size="large">
Submit
</Button>
);
expect(container.firstChild).toMatchSnapshot();
});
});
Snapshot Benefits
- Regression Detection: Catches unintended UI changes
- Documentation: Serves as visual documentation
- Quick Feedback: Fast to write and maintain
- Refactoring Safety: Ensures refactoring doesn't break UI
Snapshot Best Practices
// Good: Focused snapshots
test('button renders correctly', () => {
const { getByRole } = render(<Button>Click me</Button>);
expect(getByRole('button')).toMatchSnapshot();
});
// Bad: Large snapshots
test('entire page renders', () => {
const { container } = render(<EntirePage />);
expect(container).toMatchSnapshot(); // Too broad
});
90. What are the differences between snapshot and unit testing?
| Aspect | Snapshot Testing | Unit Testing |
|---|---|---|
| Purpose | Visual regression | Behavior verification |
| Maintenance | Requires snapshot updates | More stable |
| Coverage | Visual changes | Functional logic |
| False Positives | Higher | Lower |
| Speed | Faster to write | More comprehensive |
Snapshot vs Unit Testing Example
// Snapshot testing
test('button appearance', () => {
const { container } = render(<Button variant="primary">Click me</Button>);
expect(container.firstChild).toMatchSnapshot();
});
// Unit testing
test('button functionality', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
expect(screen.getByRole('button')).toBeEnabled();
});
Combined Approach
describe('Button Component', () => {
// Snapshot for visual regression
test('matches visual snapshot', () => {
const { container } = render(<Button>Click me</Button>);
expect(container.firstChild).toMatchSnapshot();
});
// Unit tests for behavior
test('handles click events', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('applies disabled state', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
91. Explain React's concurrent features and Suspense
Create React App is deprecated for new applications. Use a current framework where its routing/data capabilities fit, or a maintained build tool such as Vite, Parcel, or Rsbuild for a client-rendered app. Reference: Create React App deprecation.
92. What are the differences between concurrent and synchronous rendering?
Answer:
| Aspect | Synchronous Rendering | Concurrent Rendering |
|---|---|---|
| Blocking | Blocks main thread | Non-blocking, interruptible |
| Priority | All updates equal | Urgent updates prioritized |
| User Experience | Can cause jank | Smoother interactions |
| Interruption | Cannot be interrupted | Can pause/resume work |
| Memory | Single tree version | Multiple tree versions |
Coding Example:
// Synchronous rendering (traditional)
function TraditionalApp() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // This blocks until complete
};
return <button onClick={handleClick}>{count}</button>;
}
// Concurrent rendering with startTransition
function ConcurrentApp() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
// Mark this as a transition (non-urgent)
startTransition(() => {
setCount(count + 1); // This can be interrupted
});
};
return (
<div>
<button onClick={handleClick} disabled={isPending}>
{count}
</button>
{isPending && <span>Updating...</span>}
</div>
);
}
93. Explain React's error boundaries and error handling
Answer: Error boundaries are React components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of crashing the entire app.
Key Features: - Catch errors in component tree - Log error information - Display fallback UI - Only catch errors in the component tree below them
Coding Example:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log error to service
console.error('Error caught by boundary:', error, errorInfo);
this.setState({
error: error,
errorInfo: errorInfo
});
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
// Functional component with error boundary
function ErrorBoundary({ children, fallback }) {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
if (hasError) {
return fallback ? fallback(error) : <div>Something went wrong</div>;
}
return (
<ErrorBoundaryComponent onError={(error) => {
setHasError(true);
setError(error);
}}>
{children}
</ErrorBoundaryComponent>
);
}
94. What is the difference between error boundaries and try-catch?
Answer:
| Aspect | Error Boundaries | Try-Catch |
|---|---|---|
| Scope | React component tree | JavaScript execution |
| Timing | Render phase, lifecycle methods | Synchronous code execution |
| Async Errors | Cannot catch async errors | Can catch async errors with async/await |
| Event Handlers | Cannot catch errors in event handlers | Can catch errors in event handlers |
| Server-Side Rendering | Work in SSR | Work in SSR |
Coding Example:
// Error Boundary - catches render errors
class ComponentWithErrorBoundary extends React.Component {
render() {
// This error will be caught by error boundary
if (this.props.shouldError) {
throw new Error('Render error');
}
return <div>Normal render</div>;
}
}
// Try-catch - catches execution errors
function ComponentWithTryCatch() {
const handleClick = () => {
try {
// This error will be caught by try-catch
if (Math.random() > 0.5) {
throw new Error('Execution error');
}
} catch (error) {
console.error('Caught in try-catch:', error);
}
};
return <button onClick={handleClick}>Click me</button>;
}
// Combining both approaches
function RobustComponent() {
const [data, setData] = useState(null);
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
} catch (error) {
// Handle async errors with try-catch
console.error('Fetch error:', error);
}
};
// Render errors handled by error boundary
if (!data) {
return <div>Loading...</div>;
}
return <div>{data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
}
95. Explain React's server-side rendering (SSR) with Next.js
Answer: Server-side rendering (SSR) in Next.js allows React components to be rendered on the server before being sent to the client, improving performance, SEO, and initial page load times.
Key Benefits: - Better SEO (search engines can crawl content) - Faster initial page load - Better performance on low-end devices - Improved Core Web Vitals
Coding Example:
// pages/index.tsx - SSR page
import { GetServerSideProps } from 'next';
interface HomeProps {
posts: Post[];
timestamp: string;
}
export default function Home({ posts, timestamp }: HomeProps) {
return (
<div>
<h1>Server-Side Rendered Page</h1>
<p>Rendered at: {timestamp}</p>
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</div>
);
}
// Server-side data fetching
export const getServerSideProps: GetServerSideProps = async (context) => {
try {
// Fetch data on the server
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
return {
props: {
posts,
timestamp: new Date().toISOString(),
},
};
} catch (error) {
return {
notFound: true, // 404 page
};
}
};
// Dynamic routes with SSR
// pages/posts/[id].tsx
export default function Post({ post }: { post: Post }) {
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
const { id } = params as { id: string };
const response = await fetch(`https://api.example.com/posts/${id}`);
const post = await response.json();
if (!post) {
return { notFound: true };
}
return {
props: { post },
};
};
96. What are the differences between SSR and CSR?
Answer:
| Aspect | Server-Side Rendering (SSR) | Client-Side Rendering (CSR) |
|---|---|---|
| Initial Load | HTML sent from server | Empty HTML, JS loads content |
| SEO | Excellent (content in HTML) | Poor (content loaded by JS) |
| Performance | Faster initial page load | Slower initial load, faster navigation |
| Server Load | Higher (renders on server) | Lower (static files) |
| Interactivity | Requires hydration | Immediate |
| Caching | Page-level caching | Bundle caching |
Coding Example:
// CSR Example (traditional React)
// App.tsx
function App() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/posts')
.then(res => res.json())
.then(data => {
setPosts(data);
setLoading(false);
});
}, []);
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
// SSR Example (Next.js)
// pages/index.tsx
export default function Home({ posts }) {
return (
<div>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
export async function getServerSideProps() {
const posts = await fetchPosts();
return { props: { posts } };
}
// Hybrid Approach (Static Generation with Client-side Updates)
// pages/posts.tsx
export default function Posts({ initialPosts }) {
const [posts, setPosts] = useState(initialPosts);
const refreshPosts = async () => {
const newPosts = await fetch('/api/posts');
setPosts(await newPosts.json());
};
return (
<div>
<button onClick={refreshPosts}>Refresh</button>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
export async function getStaticProps() {
const posts = await fetchPosts();
return {
props: { initialPosts: posts },
revalidate: 60, // Revalidate every 60 seconds
};
}
97. Explain React's static site generation (SSG)
Answer: Static Site Generation (SSG) pre-builds pages at build time, creating static HTML files that can be served directly without server-side processing. This provides the best performance and scalability.
Key Benefits: - Fastest possible page loads - Excellent SEO - Low server costs - High scalability - CDN-friendly
Coding Example:
// pages/index.tsx - Static generation
import { GetStaticProps } from 'next';
interface HomeProps {
posts: Post[];
lastUpdated: string;
}
export default function Home({ posts, lastUpdated }: HomeProps) {
return (
<div>
<h1>Static Site</h1>
<p>Last updated: {lastUpdated}</p>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
// Static generation at build time
export const getStaticProps: GetStaticProps = async () => {
const posts = await fetchPosts();
return {
props: {
posts,
lastUpdated: new Date().toISOString(),
},
// Revalidate every hour
revalidate: 3600,
};
};
// Dynamic routes with static generation
// pages/posts/[slug].tsx
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
// Generate static paths for all posts
export async function getStaticPaths() {
const posts = await fetchPosts();
const paths = posts.map(post => ({
params: { slug: post.slug },
}));
return {
paths,
fallback: 'blocking', // or false, or 'blocking'
};
}
export async function getStaticProps({ params }) {
const post = await fetchPostBySlug(params.slug);
if (!post) {
return { notFound: true };
}
return {
props: { post },
revalidate: 60,
};
}
// Incremental Static Regeneration (ISR)
// pages/products/[id].tsx
export default function Product({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
</div>
);
}
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
return {
props: { product },
revalidate: 10, // Regenerate page every 10 seconds if requested
};
}
98. What is the difference between SSG and SSR?
Answer:
| Aspect | Static Site Generation (SSG) | Server-Side Rendering (SSR) |
|---|---|---|
| Build Time | Pages built at build time | Pages rendered on each request |
| Performance | Fastest (pre-built HTML) | Fast (server-rendered HTML) |
| Server Load | Minimal (static files) | High (rendering on each request) |
| Dynamic Content | Limited (build-time data) | Full (request-time data) |
| Caching | Excellent (CDN-friendly) | Good (page-level caching) |
| Cost | Lowest (static hosting) | Higher (server resources) |
Coding Example:
// SSG Example
// pages/blog/[slug].tsx
export default function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
export async function getStaticPaths() {
const posts = await fetchAllPosts();
return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const post = await fetchPostBySlug(params.slug);
return {
props: { post },
revalidate: 3600, // Regenerate every hour
};
}
// SSR Example
// pages/dashboard.tsx
export default function Dashboard({ userData }) {
return (
<div>
<h1>Welcome, {userData.name}</h1>
<p>Your balance: ${userData.balance}</p>
</div>
);
}
export async function getServerSideProps({ req }) {
// Get user data from request (cookies, headers, etc.)
const userData = await fetchUserData(req.cookies.token);
return {
props: { userData },
};
}
// Hybrid approach - SSG with client-side data fetching
// pages/products.tsx
export default function Products({ initialProducts }) {
const [products, setProducts] = useState(initialProducts);
const [loading, setLoading] = useState(false);
const refreshProducts = async () => {
setLoading(true);
const newProducts = await fetch('/api/products');
setProducts(await newProducts.json());
setLoading(false);
};
return (
<div>
<button onClick={refreshProducts} disabled={loading}>
{loading ? 'Refreshing...' : 'Refresh Products'}
</button>
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
export async function getStaticProps() {
const products = await fetchProducts();
return {
props: { initialProducts: products },
revalidate: 300, // Regenerate every 5 minutes
};
}
99. Explain React's micro-frontend architecture
Answer: Micro-frontends are an architectural pattern where a frontend application is decomposed into smaller, semi-independent applications that can be developed, tested, and deployed independently.
Key Benefits: - Independent development teams - Technology diversity - Independent deployments - Scalable development
Coding Example:
// Host Application (Shell)
// App.tsx
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Remote micro-frontends
const ProductApp = React.lazy(() => import('product-app/App'));
const UserApp = React.lazy(() => import('user-app/App'));
const OrderApp = React.lazy(() => import('order-app/App'));
function App() {
return (
<BrowserRouter>
<div className="shell-app">
<header>
<nav>
<a href="/products">Products</a>
<a href="/users">Users</a>
<a href="/orders">Orders</a>
</nav>
</header>
<main>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/products/*" element={<ProductApp />} />
<Route path="/users/*" element={<UserApp />} />
<Route path="/orders/*" element={<OrderApp />} />
</Routes>
</Suspense>
</main>
</div>
</BrowserRouter>
);
}
// Module Federation (Webpack 5)
// webpack.config.js (Host)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
productApp: 'productApp@http://localhost:3001/remoteEntry.js',
userApp: 'userApp@http://localhost:3002/remoteEntry.js',
orderApp: 'orderApp@http://localhost:3003/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
// Micro-frontend (Product App)
// ProductApp.tsx
import React from 'react';
import { Routes, Route } from 'react-router-dom';
function ProductList() {
return <div>Product List Component</div>;
}
function ProductDetail() {
return <div>Product Detail Component</div>;
}
export default function ProductApp() {
return (
<Routes>
<Route path="/" element={<ProductList />} />
<Route path="/:id" element={<ProductDetail />} />
</Routes>
);
}
// Shared State Management
// store/globalStore.ts
import { create } from 'zustand';
interface GlobalState {
user: User | null;
setUser: (user: User) => void;
theme: 'light' | 'dark';
setTheme: (theme: 'light' | 'dark') => void;
}
export const useGlobalStore = create<GlobalState>((set) => ({
user: null,
setUser: (user) => set({ user }),
theme: 'light',
setTheme: (theme) => set({ theme }),
}));
// Communication between micro-frontends
// utils/eventBus.ts
class EventBus {
private listeners: Record<string, Function[]> = {};
on(event: string, callback: Function) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
emit(event: string, data: any) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
}
export const eventBus = new EventBus();
// Usage in micro-frontend
eventBus.on('user-logged-in', (user) => {
console.log('User logged in:', user);
});
eventBus.emit('product-selected', { productId: 123 });
100. What are the differences between micro-frontends and monolithic apps?
Answer:
| Aspect | Micro-frontends | Monolithic Apps |
|---|---|---|
| Architecture | Distributed, independent apps | Single, unified application |
| Team Structure | Multiple independent teams | Single team or large team |
| Technology | Multiple frameworks possible | Single technology stack |
| Deployment | Independent deployments | Coordinated deployments |
| Scaling | Scale teams independently | Scale entire application |
| Complexity | Higher operational complexity | Lower operational complexity |
Coding Example:
// Monolithic App Structure
// App.tsx
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ProductList from './components/ProductList';
import UserProfile from './components/UserProfile';
import OrderHistory from './components/OrderHistory';
function App() {
return (
<BrowserRouter>
<div className="monolithic-app">
<header>
<nav>
<a href="/products">Products</a>
<a href="/profile">Profile</a>
<a href="/orders">Orders</a>
</nav>
</header>
<main>
<Routes>
<Route path="/products" element={<ProductList />} />
<Route path="/profile" element={<UserProfile />} />
<Route path="/orders" element={<OrderHistory />} />
</Routes>
</main>
</div>
</BrowserRouter>
);
}
// Micro-frontend Structure
// Shell App
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const ProductApp = React.lazy(() => import('product-app/App'));
const UserApp = React.lazy(() => import('user-app/App'));
const OrderApp = React.lazy(() => import('order-app/App'));
function ShellApp() {
return (
<BrowserRouter>
<div className="shell-app">
<header>
<nav>
<a href="/products">Products</a>
<a href="/users">Users</a>
<a href="/orders">Orders</a>
</nav>
</header>
<main>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/products/*" element={<ProductApp />} />
<Route path="/users/*" element={<UserApp />} />
<Route path="/orders/*" element={<OrderApp />} />
</Routes>
</Suspense>
</main>
</div>
</BrowserRouter>
);
}
// Shared Components Library
// shared-components/Button.tsx
import React from 'react';
interface ButtonProps {
variant: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({ variant, children, onClick }) => {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{children}
</button>
);
};
// Micro-frontend with shared components
// product-app/ProductList.tsx
import React from 'react';
import { Button } from 'shared-components/Button';
export function ProductList() {
return (
<div>
<h1>Products</h1>
<Button variant="primary">Add Product</Button>
{/* Product list content */}
</div>
);
}
// Build Configuration Comparison
// Monolithic webpack.config.js
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
// Single build process
};
// Micro-frontend webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
plugins: [
new ModuleFederationPlugin({
name: 'productApp',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};