React Patterns
Common React patterns and best practices
1. Controlled vs Uncontrolled Components
beginnerExplain the difference between controlled and uncontrolled components in React. Provide examples of each and when you would use one over the other. What are the advantages and disadvantages of each approach?
// Controlled Component
function ControlledForm() {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Controlled value:", value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
);
}
// Uncontrolled Component
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log("Uncontrolled value:", inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef}
defaultValue="initial"
/>
<button type="submit">Submit</button>
</form>
);
}
// When to use controlled:
// - Form validation in real-time
// - Dynamic form fields
// - Complex forms with conditional logic
// - When you need to manipulate the value before submission
// When to use uncontrolled:
// - Simple forms
// - File inputs (always uncontrolled)
// - Performance-critical forms with many inputs
// - When integrating with non-React codeAnswer: Controlled Component: React state manages the input value Uncontrolled Component: DOM manages the input value via ref Use controlled for validation, dynamic behavior Use uncontrolled for simple forms, file inputs, performance
KEY DIFFERENCES: CONTROLLED COMPONENTS: - Value stored in React state - onChange updates state, re-renders component - Single source of truth - Enables real-time validation - Enables dynamic form behavior - Easier to test UNCONTROLLED COMPONENTS: - Value stored in DOM - Ref used to access value on submit - Less re-renders (better performance) - Simpler for basic forms - Required for file inputs - Can integrate with non-React libraries REAL-WORLD USE CASES: Controlled: - Search with autocomplete - Form with conditional fields - Multi-step forms - Complex validation Uncontrolled: - Simple contact form - File upload - Integration with jQuery plugins - Performance-critical tables with many inputs PERFORMANCE IMPACT: - Controlled: Re-renders on every keystroke - Uncontrolled: No re-renders until submit - For large forms, uncontrolled can be 10x faster BEST PRACTICES: - Default to controlled for most forms - Use uncontrolled when performance matters - File inputs must be uncontrolled - Consider hybrid approach for large forms ASKED AT: Meta, Google, Amazon, Airbnb (React fundamentals)
2. Higher Order Components Pattern
intermediateImplement a Higher Order Component (HOC) that adds loading state management to any component. The HOC should: 1. Show a spinner while data is loading 2. Pass loading state as a prop 3. Handle errors gracefully 4. Be reusable across different components Example usage: const UserProfileWithLoader = withLoader(UserProfile, fetchUserData);
function withLoader(WrappedComponent, fetchData) {
return function WithLoaderComponent(props) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const result = await fetchData(props);
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
loadData();
}, [props]);
if (loading) {
return <div className="spinner">Loading...</div>;
}
if (error) {
return <div className="error">Error: {error}</div>;
}
return <WrappedComponent {...props} data={data} loading={loading} />;
};
}
// Usage example
function UserProfile({ user, loading }) {
if (loading) return <div>Loading user...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
const fetchUserData = async ({ userId }) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
};
const UserProfileWithLoader = withLoader(UserProfile, fetchUserData);
// In parent component
function App() {
return <UserProfileWithLoader userId={123} />;
}
// Alternative: Render Props version
function Loader({ fetchData, children }) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData).finally(() => setLoading(false));
}, [fetchData]);
return children({ loading, data });
}Answer: HOC returns new component with loading state Shows spinner while loading, passes data as prop Handles errors gracefully Reusable across different components
HIGHER ORDER COMPONENTS: Functions that take a component and return an enhanced component. ADVANTAGES: - Code reuse across components - Separation of concerns - Can modify component behavior - Compatible with class and function components DISADVANTAGES: - Prop collisions possible - Can create wrapper hell - Harder to debug in DevTools - Less intuitive than hooks MODERN ALTERNATIVES: 1. Custom Hooks (preferred in modern React) 2. Render Props pattern 3. Component Composition USE CASES FOR HOC: - Authentication/Authorization - Logging/Analytics - Error boundaries - Loading states - Data fetching BEST PRACTICES: - Use meaningful displayName for debugging - Pass through unrelated props - Avoid prop name collisions - Consider using hooks for new code REAL-WORLD EXAMPLES: - React-Redux connect() - React Router withRouter() - Material-UI withStyles() HOOKS VS HOC: - Hooks: More flexible, less nesting - HOC: Works with class components - Both can achieve similar results ASKED AT: Meta, Netflix, Uber (legacy codebases)
3. Custom Hook for Data Fetching with Cache
intermediateCreate a custom hook called useFetch that handles: 1. Data fetching with loading/error states 2. Caching responses to avoid duplicate requests 3. Automatic cleanup on unmount 4. Dependency-based refetching 5. Abort controller for canceling requests Example usage: const { data, loading, error } = useFetch("/api/users", { method: "GET" });
function useFetch(url, options = {}, dependencies = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Cache using useRef (could use Context/Redux for global cache)
const cache = useRef({});
useEffect(() => {
const abortController = new AbortController();
const fetchData = async () => {
setLoading(true);
setError(null);
// Check cache first
const cacheKey = url + JSON.stringify(options);
if (cache.current[cacheKey]) {
setData(cache.current[cacheKey]);
setLoading(false);
return;
}
try {
const response = await fetch(url, {
...options,
signal: abortController.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Cache the result
cache.current[cacheKey] = result;
setData(result);
} catch (err) {
if (err.name !== "AbortError") {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchData();
// Cleanup function
return () => {
abortController.abort();
};
}, [url, JSON.stringify(options), ...dependencies]);
return { data, loading, error };
}
// Usage examples
function UserList() {
const { data: users, loading, error } = useFetch("/api/users");
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// With dependencies
function UserProfile({ userId }) {
const { data: user } = useFetch(
`/api/users/${userId}`,
{ method: "GET" },
[userId] // Refetch when userId changes
);
}
// Advanced: Return refetch function
// Add to useFetch:
const [trigger, setTrigger] = useState(0);
const refetch = () => setTrigger(t => t + 1);
// Add trigger to dependenciesAnswer: Custom hook returns { data, loading, error } Implements caching to avoid duplicate requests Uses AbortController for cleanup Refetches when dependencies change
CUSTOM HOOK PATTERN: Extract reusable logic from components into hooks. KEY FEATURES IMPLEMENTED: 1. CACHING: - Store responses in useRef - Avoid duplicate network requests - Simple in-memory cache - Could be enhanced with TTL 2. ABORT CONTROLLER: - Cancel requests on unmount - Prevent state updates on unmounted components - Avoid memory leaks 3. DEPENDENCY TRACKING: - Refetch when URL/options change - Manual refetch capability - Prevent infinite loops 4. ERROR HANDLING: - HTTP error status handling - Network error handling - Clean error states PERFORMANCE OPTIMIZATIONS: - useRef for cache (doesn't cause re-renders) - JSON.stringify for dependency comparison - AbortController for cleanup - Conditional fetching PRODUCTION CONSIDERATIONS: - Add retry logic with exponential backoff - Implement stale-while-revalidate - Add request deduplication - Consider using SWR or React Query ALTERNATIVE LIBRARIES: - React Query (TanStack Query) - SWR (Stale-While-Revalidate) - Apollo Client (GraphQL) - RTK Query (Redux Toolkit) USE CASES: - API data fetching - Form submissions - Real-time updates - Paginated data ASKED AT: Meta, Google, Netflix (modern React patterns)
4. Compound Components Pattern
advancedImplement a Tabs component using the compound components pattern. The component should: 1. Allow flexible composition of Tab and TabPanel 2. Manage active tab state internally 3. Support keyboard navigation (arrow keys) 4. Be accessible (ARIA attributes) Example usage: <Tabs> <TabList> <Tab>First</Tab> <Tab>Second</Tab> </TabList> <TabPanel>First content</TabPanel> <TabPanel>Second content</TabPanel> </Tabs>
import { createContext, useContext, useState } from "react";
const TabsContext = createContext();
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
const value = {
activeIndex,
setActiveIndex
};
return (
<TabsContext.Provider value={value}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
const handleKeyDown = (e) => {
const tabs = React.Children.toArray(children);
const currentIndex = activeIndex;
switch (e.key) {
case "ArrowRight":
e.preventDefault();
setActiveIndex((currentIndex + 1) % tabs.length);
break;
case "ArrowLeft":
e.preventDefault();
setActiveIndex((currentIndex - 1 + tabs.length) % tabs.length);
break;
case "Home":
e.preventDefault();
setActiveIndex(0);
break;
case "End":
e.preventDefault();
setActiveIndex(tabs.length - 1);
break;
}
};
return (
<div
role="tablist"
className="tab-list"
onKeyDown={handleKeyDown}
>
{children}
</div>
);
}
function Tab({ children, index }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
const isActive = activeIndex === index;
return (
<button
role="tab"
aria-selected={isActive}
className={`tab ${isActive ? "active" : ""}`}
onClick={() => setActiveIndex(index)}
tabIndex={isActive ? 0 : -1}
>
{children}
</button>
);
}
function TabPanel({ children, index }) {
const { activeIndex } = useContext(TabsContext);
const isActive = activeIndex === index;
return (
<div
role="tabpanel"
aria-hidden={!isActive}
className={`tab-panel ${isActive ? "active" : ""}`}
tabIndex={0}
>
{isActive && children}
</div>
);
}
// Usage
function App() {
return (
<Tabs defaultIndex={1}>
<TabList>
<Tab index={0}>Profile</Tab>
<Tab index={1}>Messages</Tab>
<Tab index={2}>Settings</Tab>
</TabList>
<TabPanel index={0}>
<h2>Profile Content</h2>
<p>Edit your profile here</p>
</TabPanel>
<TabPanel index={1}>
<h2>Messages</h2>
<p>Your messages will appear here</p>
</TabPanel>
<TabPanel index={2}>
<h2>Settings</h2>
<p>Configure your settings</p>
</TabPanel>
</Tabs>
);
}
// Alternative: Clone children with props
function TabList({ children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
return (
<div role="tablist">
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isActive: activeIndex === index,
onClick: () => setActiveIndex(index),
index
})
)}
</div>
);
}Answer: Compound components share state via Context Tabs manages active state, TabList/Tab/TabPanel consume Keyboard navigation with arrow keys ARIA attributes for accessibility
COMPOUND COMPONENTS PATTERN: Components that work together through implicit state sharing. BENEFITS: - Flexible API (users can reorder components) - Implicit state management - Clean separation of concerns - Good for complex UI components HOW IT WORKS: 1. Parent component (Tabs) provides Context 2. Child components (Tab, TabPanel) consume Context 3. State is shared implicitly 4. Users compose components as needed KEY IMPLEMENTATION DETAILS: - React Context for state sharing - React.Children.map + cloneElement (alternative) - ARIA attributes for accessibility - Keyboard navigation ALTERNATIVE APPROACHES: 1. Clone Element Pattern: Parent clones children with props 2. Render Props: More explicit but verbose 3. Hooks: useTabs() hook returns props USE CASES: - Tab components - Accordion/Expandable sections - Form fields with labels/errors - Data tables with sorting/filtering - Modal/dialog systems ACCESSIBILITY FEATURES: - role="tablist", role="tab", role="tabpanel" - aria-selected, aria-hidden - Keyboard navigation (arrows, Home, End) - Focus management PERFORMANCE CONSIDERATIONS: - Context triggers re-renders for all consumers - Memoize child components if needed - Consider using multiple contexts REAL-WORLD EXAMPLES: - Reach UI Tabs - Chakra UI components - Material-UI Tabs ASKED AT: Meta, Google, Microsoft (component design)
5. Render Props Pattern for Mouse Tracking
intermediateImplement a MouseTracker component using the render props pattern. The component should: 1. Track mouse position 2. Accept a render prop function 3. Handle cleanup on unmount 4. Be reusable for different rendering needs Example usage: <MouseTracker> {({ x, y }) => ( <div> Mouse position: {x}, {y} </div> )} </MouseTracker>
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return render(position);
}
// Usage example
function App() {
return (
<div style={{ height: "100vh" }}>
<MouseTracker
render={({ x, y }) => (
<div>
<h1>Mouse Position Tracker</h1>
<p>
X: {x}, Y: {y}
</p>
<div
style={{
position: "absolute",
left: x,
top: y,
width: "20px",
height: "20px",
backgroundColor: "red",
borderRadius: "50%",
transform: "translate(-50%, -50%)"
}}
/>
</div>
)}
/>
</div>
);
}
// Alternative: Children as function (more common)
function MouseTracker({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return children(position);
}
// Usage with children
function App() {
return (
<MouseTracker>
{({ x, y }) => (
<div>Mouse at: {x}, {y}</div>
)}
</MouseTracker>
);
}
// Multiple render props example
function DataFetcher({ url, render, renderLoading, renderError }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
if (loading && renderLoading) {
return renderLoading();
}
if (error && renderError) {
return renderError(error);
}
return render(data);
}Answer: Render props pattern passes render function as prop MouseTracker tracks position, passes to render function Children-as-function variant more common Cleanup with removeEventListener
RENDER PROPS PATTERN: Component that accepts a function as prop and calls it with internal state. ADVANTAGES: - Highly flexible rendering - Reusable behavior logic - Easy to test - Explicit data flow DISADVANTAGES: - Can lead to nesting/callback hell - Slightly verbose - Performance concerns with inline functions COMMON PATTERNS: 1. Single render prop: <Component render={data => (...)} /> 2. Children as function: <Component> {data => (...)} </Component> 3. Multiple render props: <Component renderLoading={() => (...)} renderError={error => (...)} render={data => (...)} /> PERFORMANCE CONSIDERATIONS: - Inline functions create new references each render - Can cause unnecessary child re-renders - Use useCallback for stable references - Consider React.memo for child components MODERN ALTERNATIVES: - Custom Hooks (most common replacement) - Higher Order Components - Compound Components USE CASES: - Mouse/touch tracking - Data fetching - Form state management - Authentication - Theme/context providers REAL-WORLD EXAMPLES: - React Router Route component - Formik render props - Downshift component BEST PRACTICES: - Prefer children-as-function for single render prop - Provide meaningful prop names - Handle loading/error states - TypeScript: Generic types for render props ASKED AT: Meta, Airbnb, Stripe (legacy codebases)
6. Optimizing Context API for Performance
advancedYou have a ThemeContext that provides theme values (colors, spacing, etc.) to many components. Users report performance issues when theme changes cause unnecessary re-renders. How would you optimize? 1. What causes unnecessary re-renders? 2. How can you prevent them? 3. Provide optimized implementation.
// Problem: All consumers re-render when any context value changes
const ThemeContext = createContext({
colors: { primary: "#000" },
spacing: { small: "8px" }
});
function ThemeProvider({ children }) {
const [theme, setTheme] = useState({
colors: { primary: "#007bff" },
spacing: { small: "8px", medium: "16px" },
mode: "light"
});
// This causes all consumers to re-render when any part changes
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Solution 1: Split contexts
const ThemeStateContext = createContext(null);
const ThemeUpdaterContext = createContext(null);
function OptimizedThemeProvider({ children }) {
const [theme, setTheme] = useState({
colors: { primary: "#007bff" },
spacing: { small: "8px" },
mode: "light"
});
// Memoize context values
const themeState = useMemo(() => theme, [theme]);
const themeUpdater = useMemo(() => setTheme, []);
return (
<ThemeStateContext.Provider value={themeState}>
<ThemeUpdaterContext.Provider value={themeUpdater}>
{children}
</ThemeUpdaterContext.Provider>
</ThemeStateContext.Provider>
);
}
// Consumers only re-render when needed
function ThemeButton() {
const theme = useContext(ThemeStateContext);
// Only re-renders when theme changes
return <button style={{ backgroundColor: theme.colors.primary }}>Click</button>;
}
function ThemeToggle() {
const setTheme = useContext(ThemeUpdaterContext);
// Doesn't re-render when theme changes, only needs updater
return (
<button onClick={() => setTheme(prev => ({
...prev,
mode: prev.mode === "light" ? "dark" : "light"
}))}>
Toggle Theme
</button>
);
}
// Solution 2: Use selectors pattern
function useThemeSelector(selector) {
const theme = useContext(ThemeStateContext);
return useMemo(() => selector(theme), [theme, selector]);
}
// Component only re-renders when primary color changes
function PrimaryButton() {
const primaryColor = useThemeSelector(theme => theme.colors.primary);
return <button style={{ backgroundColor: primaryColor }}>Click</button>;
}
// Solution 3: Zustand/Recoil style atoms
const themeAtoms = {
colors: atom({ primary: "#007bff" }),
spacing: atom({ small: "8px" }),
mode: atom("light")
};
// Components subscribe only to needed atomsAnswer: Split contexts: Separate state from updaters Memoize context values with useMemo Use selectors to subscribe to specific values Consider state management libraries for complex cases
CONTEXT API PERFORMANCE ISSUES: PROBLEMS: 1. All consumers re-render when any context value changes 2. Inline objects/functions create new references 3. Deeply nested consumers cause cascade re-renders 4. No fine-grained subscription capability OPTIMIZATION STRATEGIES: 1. SPLIT CONTEXTS: - Separate state from updaters - Separate frequently-changing from stable values - Example: UserContext → UserStateContext + UserDispatchContext 2. MEMOIZATION: - useMemo for context values - Stable function references with useCallback - Prevent unnecessary value changes 3. SELECTOR PATTERN: - Custom hooks that return specific values - Components subscribe to slices of state - Redux-style selectors for context 4. STATE NORMALIZATION: - Flatten nested state structures - Use IDs instead of nested objects - Easier to track changes 5. SKIP UNNECESSARY RE-RENDERS: - React.memo for consumer components - useMemo for expensive computations - ShouldComponentUpdate for class components ADVANCED PATTERNS: 1. Observer Pattern: Custom subscription system 2. Proxy-based tracking: Track accessed properties 3. Compiler optimizations: React Forget (future) WHEN TO USE CONTEXT VS STATE MANAGEMENT: - Context: Theme, Auth, Feature flags - Redux/Zustand: Complex app state, DevTools, Middleware - Recoil/Jotai: Derived state, Async state MEASUREMENT TOOLS: - React DevTools Profiler - Why did you re-render? library - Console logs with performance markers PRODUCTION EXAMPLE: - Next.js uses split contexts for router/theme - Chakra UI optimizes theme context - Formik uses selectors for form state ASKED AT: Meta, Google, Microsoft (performance optimization)
7. When to Use React.memo, useMemo, useCallback
intermediateExplain the differences between React.memo, useMemo, and useCallback. When should each be used and what are the performance implications? Provide concrete examples where each optimization helps and where it might hurt performance.
// React.memo: Memoizes component
const ExpensiveComponent = React.memo(function ExpensiveComponent({ items, onSelect }) {
console.log("ExpensiveComponent rendered");
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onSelect(item.id)}>
{item.name}
</li>
))}
</ul>
);
});
// Custom comparison function (rarely needed)
const MemoizedWithCompare = React.memo(ExpensiveComponent, (prevProps, nextProps) => {
// Return true if props are equal (no re-render needed)
return prevProps.items.length === nextProps.items.length;
});
// useMemo: Memoizes expensive calculations
function UserList({ users, searchTerm }) {
const filteredUsers = useMemo(() => {
console.log("Filtering users...");
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]); // Recalculate when users or searchTerm changes
return (
<div>
<div>Count: {filteredUsers.length}</div>
<ExpensiveComponent items={filteredUsers} />
</div>
);
}
// useCallback: Memoizes function references
function ParentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// Without useCallback: new function each render
// const handleSelect = (id) => { console.log(id); };
// With useCallback: stable function reference
const handleSelect = useCallback((id) => {
console.log("Selected:", id);
}, []); // Empty deps: function never changes
// With dependencies
const handleSelectWithDeps = useCallback((id) => {
console.log("Selected item", id, "from", items.length, "items");
}, [items]); // Recreate when items changes
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
Rerender Parent ({count})
</button>
<ExpensiveComponent
items={items}
onSelect={handleSelect} // Stable reference prevents child re-renders
/>
</div>
);
}
// Example where memoization HURTS performance
function SimpleComponent({ text }) {
// BAD: useMemo for simple concatenation
// const message = useMemo(() => `Hello ${text}`, [text]);
// GOOD: Direct computation
const message = `Hello ${text}`;
return <div>{message}</div>;
}Answer: React.memo: Memoizes component re-renders useMemo: Memoizes expensive calculations useCallback: Memoizes function references Use when performance benefits outweigh overhead
PERFORMANCE OPTIMIZATION TRIO: REACT.MEMO: - Memoizes component output - Prevents re-renders when props don't change - Use for: Heavy components, Pure presentational components - Avoid for: Simple components, Components with frequently changing props - Custom comparison function for complex props USE MEMO: - Memoizes computed values - Use for: Expensive calculations, Filtering/sorting arrays, Object/array transformations - Avoid for: Simple calculations, Primitives, Small arrays - Rule of thumb: Computation takes >1ms or creates new references USE CALLBACK: - Memoizes function references - Use for: Prop functions passed to memoized children, Event handlers in effects dependencies - Avoid for: Functions that don't cause re-renders, Simple inline handlers - Note: Inline functions are cheap to create but cause re-renders PERFORMANCE TRADEOFFS: 1. MEMORY OVERHEAD: Memoization stores previous values 2. COMPUTATION OVERHEAD: Comparison functions run 3. COMPLEXITY: Harder to debug and reason about 4. PREMATURE OPTIMIZATION: Can make code slower WHEN TO OPTIMIZE: 1. Measured performance issues (Profiler shows bottlenecks) 2. Large lists or tables 3. Animation-heavy components 4. Real-time data updates MEASUREMENT TOOLS: - React DevTools Profiler - Chrome Performance tab - console.time() / console.timeEnd() - React.memo with why-did-you-render COMMON MISTAKES: 1. Memoizing everything (over-optimization) 2. Incorrect dependency arrays 3. Creating new objects in render anyway 4. Not measuring actual performance BEST PRACTICES: 1. Profile before optimizing 2. Start with React.memo at leaf components 3. Use useMemo for derived state 4. Use useCallback for prop functions 5. Test with production builds (dev mode is slower) ASKED AT: Meta, Google, Amazon (performance-focused roles)
8. Error Boundaries Implementation
intermediateImplement an ErrorBoundary component that: 1. Catches JavaScript errors in child components 2. Displays a fallback UI instead of crashing 3. Logs errors to an error reporting service 4. Provides a way to reset the error state Also implement a custom hook useErrorHandler for functional components.
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 shows the fallback UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Log the error to an error reporting service
console.error("ErrorBoundary caught an error:", error, errorInfo);
// Example: Send to error reporting service
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
this.setState({ errorInfo });
}
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
if (this.props.onReset) {
this.props.onReset();
}
};
render() {
if (this.state.hasError) {
// Custom fallback UI
if (this.props.fallback) {
return this.props.fallback({
error: this.state.error,
errorInfo: this.state.errorInfo,
resetError: this.handleReset
});
}
// Default fallback
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<details>
<summary>Error details</summary>
<pre>{this.state.error?.toString()}</pre>
<pre>{this.state.errorInfo?.componentStack}</pre>
</details>
<button onClick={this.handleReset}>Try again</button>
</div>
);
}
return this.props.children;
}
}
// Usage examples
function App() {
return (
<ErrorBoundary
onError={(error, errorInfo) => {
// Send to Sentry/LogRocket
console.error("App error:", error, errorInfo);
}}
fallback={({ error, resetError }) => (
<div>
<h1>Custom Error UI</h1>
<p>{error.message}</p>
<button onClick={resetError}>Retry</button>
</div>
)}
>
<BuggyComponent />
</ErrorBoundary>
);
}
// Custom hook for functional components
function useErrorHandler(givenError) {
const [error, setError] = useState(null);
useEffect(() => {
if (givenError) {
setError(givenError);
}
}, [givenError]);
const handleError = useCallback((error) => {
setError(error);
// Log to service
console.error("useErrorHandler caught:", error);
}, []);
const resetError = useCallback(() => {
setError(null);
}, []);
return { error, handleError, resetError };
}
// Hook usage
function ComponentWithErrorHandling() {
const { error, handleError, resetError } = useErrorHandler();
const doSomethingRisky = useCallback(async () => {
try {
await riskyOperation();
} catch (err) {
handleError(err);
}
}, [handleError]);
if (error) {
return (
<div>
<p>Error: {error.message}</p>
<button onClick={resetError}>Dismiss</button>
</div>
);
}
return <button onClick={doSomethingRisky}>Do Risky Thing</button>;
}
// Nested error boundaries for granular error handling
function App() {
return (
<ErrorBoundary fallback={<GlobalError />}>
<Header />
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<ContentError />}>
<MainContent />
</ErrorBoundary>
</ErrorBoundary>
);
}Answer: ErrorBoundary class component with getDerivedStateFromError and componentDidCatch Custom fallback UI and reset functionality useErrorHandler hook for functional components Nested boundaries for granular error recovery
ERROR BOUNDARIES PATTERN: React components that catch JavaScript errors in their child component tree. LIMITATIONS: - Only class components can be error boundaries - Catches errors during: render, lifecycle methods, constructors - Does NOT catch: Event handlers, Async code, SSR errors, Errors in error boundary itself KEY METHODS: 1. getDerivedStateFromError(): Update state for fallback UI 2. componentDidCatch(): Log errors, side effects PRODUCTION PATTERNS: 1. GRANULAR ERROR BOUNDARIES: - Wrap independent features - Prevent entire app from crashing - Example: Header, Sidebar, Main content separate 2. ERROR REPORTING: - Integrate with Sentry, LogRocket - Include user context - Track error frequency 3. RECOVERY STRATEGIES: - Retry button - Automatic retry with exponential backoff - Fallback to cached data 4. USER EXPERIENCE: - Friendly error messages - Contact support option - Save user work before error HOOKS WORKAROUND: Since hooks can't be error boundaries: - useErrorHandler for async operations - Try/catch in event handlers - Error state management REAL-WORLD IMPLEMENTATIONS: 1. Next.js: Built-in error boundaries 2. Create React App: Error overlay in development 3. React Router: Error boundary for route components TESTING STRATEGIES: - Mock component that throws - Test error recovery flow - Test logging integration BEST PRACTICES: - Use at least one top-level boundary - Add boundaries for independent features - Log to external service - Provide recovery options ASKED AT: Meta, Google, Netflix (production React)
9. Comparing State Management Solutions
advancedCompare different state management solutions for React: 1. useState/useReducer 2. Context API 3. Redux (with Redux Toolkit) 4. Zustand 5. Recoil 6. MobX For each, explain: - When to use it - Pros and cons - Typical use case - Learning curve
// 1. useState/useReducer (Built-in)
function ComponentWithState() {
const [count, setCount] = useState(0);
// useReducer for complex state logic
const [state, dispatch] = useReducer(reducer, initialState);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// 2. Context API
const UserContext = createContext();
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<ChildComponent />
</UserContext.Provider>
);
}
// 3. Redux Toolkit (Modern Redux)
import { configureStore, createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1
}
});
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// 4. Zustand
import create from "zustand";
const useStore = create((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
reset: () => set({ count: 0 })
}));
function Component() {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
}
// 5. Recoil
import { atom, selector, useRecoilState } from "recoil";
const countState = atom({
key: "countState",
default: 0
});
const doubledCount = selector({
key: "doubledCount",
get: ({ get }) => get(countState) * 2
});
function Component() {
const [count, setCount] = useRecoilState(countState);
const double = useRecoilValue(doubledCount);
return <div>{count} * 2 = {double}</div>;
}
// 6. MobX
import { makeAutoObservable } from "mobx";
import { observer } from "mobx-react-lite";
class CounterStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count++;
}
}
const store = new CounterStore();
const Counter = observer(() => (
<button onClick={() => store.increment()}>
{store.count}
</button>
));Answer: useState/useReducer: Component state, simple cases Context: Theme/auth, medium apps, prop drilling Redux: Large apps, debugging, predictable updates Zustand: Simple global state, minimal boilerplate Recoil: Complex derived state, atom-based MobX: Observable pattern, OOP style
STATE MANAGEMENT DECISION TREE: 1. LOCAL STATE (useState/useReducer): - Single component state - Simple forms - UI state (toggles, modals) - Pros: Simple, no dependencies - Cons: Doesn't scale, prop drilling 2. CONTEXT API: - Theme/authentication - Small to medium apps - Avoiding prop drilling - Pros: Built-in, simple API - Cons: Performance issues, re-render all consumers 3. REDUX / REDUX TOOLKIT: - Large enterprise applications - Complex state with many reducers - Need for time-travel debugging - Pros: Predictable, middleware, DevTools - Cons: Boilerplate, steep learning curve 4. ZUSTAND: - Simpler alternative to Redux - Small to medium apps - Quick prototyping - Pros: Minimal boilerplate, hooks-based - Cons: Smaller ecosystem 5. RECOIL: - Complex derived/async state - Experimental features needed - Facebook projects - Pros: Atom-based, async support - Cons: Experimental, Facebook-specific 6. MOBX: - Observable pattern lovers - OOP background teams - Real-time updates - Pros: Simple mental model, automatic reactivity - Cons: Magic, less predictable SELECTION CRITERIA: 1. App Size: Small (Context) → Large (Redux) 2. Team Experience: Known tool vs learning new 3. Performance Needs: Fine-grained vs batched updates 4. Debugging Needs: Redux DevTools vs simple logging 5. Future Scaling: Will state needs grow? HYBRID APPROACH: Most apps use multiple solutions: - useState for local UI state - Context for theme/auth - Redux/Zustand for business logic MIGRATION PATH: Start with built-in, add libraries as needed: useState → Context → Zustand → Redux RECOMMENDATIONS 2024: - New projects: Zustand or Redux Toolkit - Existing Redux: Stay with Redux Toolkit - Simple apps: Context + useState - Complex derived state: Recoil/Jotai ASKED AT: Meta, Google, Amazon (architecture roles)
10. React Server Components Implementation
advancedExplain React Server Components (RSC) and demonstrate how to implement them. What problems do they solve? How do they differ from Server-Side Rendering (SSR)? Show example of: 1. Server Component fetching data 2. Client Component with interactivity 3. How they work together
// Server Component (app/page.js in Next.js 13+)
// This runs on the server, can directly access databases
import { db } from "@/lib/db";
import ClientComponent from "@/components/ClientComponent";
async function ServerComponent() {
// Direct database access - no API route needed
const products = await db.products.findMany({
take: 10,
orderBy: { createdAt: "desc" }
});
// No useState, useEffect, or event handlers here
return (
<div>
<h1>Product List (Server Rendered)</h1>
{/* Server component rendering */}
<ul>
{products.map(product => (
<li key={product.id}>
<h2>{product.name}</h2>
<p>{product.description}</p>
<span>${product.price}</span>
</li>
))}
</ul>
{/* Client component for interactivity */}
<ClientComponent initialProducts={products} />
</div>
);
}
// Client Component (components/ClientComponent.jsx)
"use client"; // Required directive
import { useState } from "react";
export default function ClientComponent({ initialProducts }) {
const [products, setProducts] = useState(initialProducts);
const [search, setSearch] = useState("");
const filteredProducts = products.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
const addToCart = async (productId) => {
// Client-side interaction
await fetch("/api/cart", {
method: "POST",
body: JSON.stringify({ productId })
});
alert("Added to cart!");
};
return (
<div>
<h2>Interactive Product Search</h2>
<input
type="text"
placeholder="Search products..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>
<h3>{product.name}</h3>
<button onClick={() => addToCart(product.id)}>
Add to Cart
</button>
</li>
))}
</ul>
</div>
);
}
// Layout Component (app/layout.js)
// This is also a Server Component by default
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>
<nav>
{/* Navigation can be server component */}
<a href="/">Home</a>
<a href="/products">Products</a>
</nav>
</header>
<main>{children}</main>
<footer>
{/* Server component fetching current year */}
<p>© {new Date().getFullYear()} My Store</p>
</footer>
</body>
</html>
);
}
// Example of nested server/client components
// app/product/[id]/page.js
import ProductDetails from "@/components/ProductDetails";
import ProductReviews from "@/components/ProductReviews";
import AddToCartButton from "@/components/AddToCartButton";
async function ProductPage({ params }) {
const product = await db.products.findUnique({
where: { id: params.id },
include: { reviews: true }
});
return (
<div>
{/* Server component fetching data */}
<ProductDetails product={product} />
{/* Client component for reviews with interactivity */}
<ProductReviews reviews={product.reviews} />
{/* Client component for cart actions */}
<AddToCartButton productId={product.id} />
</div>
);
}Answer: Server Components: Run on server, no client JS, direct data access Client Components: "use client" directive, interactivity, state RSC solves: Bundle size, data fetching waterfalls, SEO Different from SSR: Zero client JS for static parts
REACT SERVER COMPONENTS (RSC): New architecture where components can render on the server and stream to client. KEY CHARACTERISTICS: SERVER COMPONENTS: - Render exclusively on server - Zero bundle size impact - Direct database/API access - No interactivity (no event handlers) - No React state/lifecycle - Can import server-only modules CLIENT COMPONENTS: - Marked with "use client" directive - Render on client (after server) - Support interactivity, state, effects - Larger bundle size - Can't import server-only modules BENEFITS: 1. REDUCED BUNDLE SIZE: - Server components don't ship to client - Libraries stay on server (Moment.js, etc.) - Faster page loads 2. DIRECT DATA ACCESS: - No need for API routes - Database calls in components - Eliminates data fetching waterfalls 3. IMPROVED SEO: - Content rendered on server - Search engines see complete content - Better performance scores 4. STREAMING: - Send HTML in chunks - Show loading states progressively - Better perceived performance VS SERVER-SIDE RENDERING (SSR): - SSR: Entire page renders on server, ships as HTML, then hydrates - RSC: Components decide where to render, partial hydration - RSC: More granular, can stream - SSR: All-or-nothing approach IMPLEMENTATION PATTERNS: 1. COLOCATE DATA FETCHING: - Fetch data where it's used - No prop drilling of data - Type-safe with TypeScript 2. PROGRESSIVE ENHANCEMENT: - Start with server components - Add client components for interactivity - Fallbacks for non-JS users 3. COMPOSITION: - Server components can render client components - Client components can't render server components - Pass server data as props to client components LIMITATIONS: - Learning curve - Requires React 18+ and framework support - Debugging can be complex - Backend knowledge needed ADOPTION: - Next.js 13+ App Router (full support) - Experimental in other frameworks - Not available in plain React BEST PRACTICES: 1. Default to server components 2. Move interactivity to client components 3. Keep client components small 4. Use Suspense for loading states 5. Cache server component results ASKED AT: Meta, Vercel, Netflix (cutting-edge React)
11. React Suspense for Data Fetching
advancedImplement data fetching with React Suspense. Create: 1. A wrapper that throws promise while loading 2. Error boundary for error handling 3. Suspense boundary with fallback 4. Cache mechanism for deduplication Example: <Suspense fallback={<Spinner />}> <UserProfile userId={123} /> </Suspense>
// 1. Create a cache for promises
const cache = new Map();
function fetchData(key, promiseFn) {
if (!cache.has(key)) {
cache.set(key, promiseFn());
}
return cache.get(key);
}
// 2. Create wrapper that throws promise
function useSuspenseFetch(key, promiseFn) {
const data = fetchData(key, promiseFn);
// Check if data is a promise (still loading)
if (data && typeof data.then === "function") {
throw data; // Suspense will catch this
}
// Check if data is an error
if (data instanceof Error) {
throw data; // Error boundary will catch this
}
return data;
}
// 3. User profile component using suspense
function UserProfile({ userId }) {
const user = useSuspenseFetch(
`user-${userId}`,
() => fetch(`/api/users/${userId}`).then(res => res.json())
);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// 4. App with Suspense boundaries
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading user...</div>}>
<UserProfile userId={123} />
<Suspense fallback={<div>Loading posts...</div>}>
<UserPosts userId={123} />
</Suspense>
</Suspense>
</ErrorBoundary>
);
}
// 5. Alternative: React.lazy for code splitting
const LazyComponent = React.lazy(() => import("./HeavyComponent"));
function App() {
return (
<Suspense fallback={<div>Loading component...</div>}>
<LazyComponent />
</Suspense>
);
}
// 6. Concurrent features: useTransition
function SearchBox() {
const [query, setQuery] = useState("");
const [deferredQuery, setDeferredQuery] = useState("");
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value); // Immediate update
startTransition(() => {
setDeferredQuery(value); // Non-urgent update
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <span>Updating...</span>}
<SearchResults query={deferredQuery} />
</div>
);
}
// 7. useDeferredValue for deferred updates
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query);
return (
<div>
<p>Showing results for: {deferredQuery}</p>
{/* Expensive computation uses deferred value */}
</div>
);
}Answer: Suspense throws promises for loading states Cache prevents duplicate requests Error boundaries handle errors useTransition/useDeferredValue for concurrent features
REACT SUSPENSE PATTERN: Declarative way to handle asynchronous operations in React. KEY CONCEPTS: 1. SUSPENSE BOUNDARY: - Catches thrown promises - Shows fallback while promise pending - Resumes rendering when promise resolves 2. ERROR BOUNDARY: - Catches thrown errors - Shows error UI - Separate from loading states 3. CACHE PATTERN: - Store promises in cache - Deduplicate requests - Enable immediate re-renders CONCURRENT FEATURES: 1. USE TRANSITION: - Mark updates as non-urgent - `isPending` shows loading state - Better user experience for slow updates 2. USE DEFERRED VALUE: - Defer updating value - Show stale content during updates - Smooth transitions 3. START TRANSITION: - Wrap state updates - Allow interruption of rendering - Improve responsiveness DATA FETCHING PATTERNS: 1. RENDER-AS-YOU-FETCH: - Start fetching early - Render as data arrives - Better than fetch-then-render 2. STREAMING SSR: - Send HTML in chunks - Suspense boundaries control streaming - Progressive loading PERFORMANCE BENEFITS: - No waterfall requests - Parallel data fetching - Immediate transitions - Non-blocking UI LIMITATIONS: - Experimental for data fetching - Requires specific patterns - Complex error handling BEST PRACTICES: 1. Place Suspense near loading content 2. Use multiple Suspense boundaries 3. Combine with Error Boundaries 4. Use for code splitting 5. Implement proper cache REAL-WORLD LIBRARIES: - React Query (TanStack Query) - SWR - Relay - Apollo Client ASKED AT: Meta, Vercel, Netflix (concurrent React)
12. React Portals for Modals and Tooltips
intermediateImplement a modal system using React Portals. Requirements: 1. Create portal to render outside DOM hierarchy 2. Manage focus and keyboard navigation 3. Handle click outside to close 4. Support multiple modals with z-index 5. Accessible with ARIA attributes
// 1. Create portal component
import { useEffect } from "react";
import { createPortal } from "react-dom";
function Portal({ children, containerId = "portal-root" }) {
const [container, setContainer] = useState(null);
useEffect(() => {
// Find or create container
let portalContainer = document.getElementById(containerId);
if (!portalContainer) {
portalContainer = document.createElement("div");
portalContainer.id = containerId;
document.body.appendChild(portalContainer);
}
setContainer(portalContainer);
return () => {
// Don't remove container to avoid recreating
// portalContainer.remove();
};
}, [containerId]);
if (!container) return null;
return createPortal(children, container);
}
// 2. Modal component using portal
function Modal({ isOpen, onClose, title, children }) {
const modalRef = useRef();
// Handle Escape key
useEffect(() => {
const handleEscape = (e) => {
if (e.key === "Escape") {
onClose();
}
};
if (isOpen) {
document.addEventListener("keydown", handleEscape);
// Prevent body scroll
document.body.style.overflow = "hidden";
// Focus trap
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements?.length > 0) {
focusableElements[0].focus();
}
}
return () => {
document.removeEventListener("keydown", handleEscape);
document.body.style.overflow = "auto";
};
}, [isOpen, onClose]);
// Handle click outside
const handleBackdropClick = (e) => {
if (e.target === e.currentTarget) {
onClose();
}
};
if (!isOpen) return null;
return (
<Portal>
<div
className="modal-backdrop"
onClick={handleBackdropClick}
role="presentation"
>
<div
ref={modalRef}
className="modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<div className="modal-header">
<h2 id="modal-title">{title}</h2>
<button
onClick={onClose}
aria-label="Close modal"
className="close-button"
>
×
</button>
</div>
<div className="modal-content">
{children}
</div>
</div>
</div>
</Portal>
);
}
// 3. Usage
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<button onClick={() => setIsModalOpen(true)}>
Open Modal
</button>
<Modal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
title="Example Modal"
>
<p>Modal content goes here.</p>
<button onClick={() => alert("Action!")}>
Action Button
</button>
</Modal>
</div>
);
}
// 4. Tooltip using portal
function Tooltip({ children, content }) {
const [isVisible, setIsVisible] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const triggerRef = useRef();
const showTooltip = () => {
const rect = triggerRef.current.getBoundingClientRect();
setPosition({
x: rect.left + rect.width / 2,
y: rect.top
});
setIsVisible(true);
};
return (
<>
<span
ref={triggerRef}
onMouseEnter={showTooltip}
onMouseLeave={() => setIsVisible(false)}
onFocus={showTooltip}
onBlur={() => setIsVisible(false)}
aria-describedby="tooltip-content"
>
{children}
</span>
{isVisible && (
<Portal>
<div
className="tooltip"
style={{
position: "fixed",
left: position.x,
top: position.y,
transform: "translate(-50%, -100%)"
}}
role="tooltip"
id="tooltip-content"
>
{content}
</div>
</Portal>
)}
</>
);
}Answer: Portals render outside DOM hierarchy Modal with focus trap and keyboard navigation Click outside to close Tooltip with positioning
REACT PORTALS PATTERN: Render children into a DOM node outside parent hierarchy. USE CASES: 1. Modals/Dialogs 2. Tooltips/Popovers 3. Dropdown menus 4. Notifications/Toasts 5. Loading overlays BENEFITS: - Escape CSS overflow:hidden - Proper z-index stacking - Clean DOM structure - Better accessibility IMPLEMENTATION DETAILS: 1. PORTAL CREATION: - Use ReactDOM.createPortal() - Create container element - Append to document.body - Cleanup on unmount 2. MODAL FEATURES: - Focus trap (first element gets focus) - Escape key to close - Click outside to close - Prevent body scroll - ARIA attributes 3. TOOLTIP FEATURES: - Position calculation - Mouse enter/leave - Focus/blur events - Arrow positioning ACCESSIBILITY REQUIREMENTS: - role="dialog" for modals - aria-modal="true" - aria-labelledby/aria-describedby - Keyboard navigation (Tab, Escape) - Screen reader announcements PERFORMANCE CONSIDERATIONS: - Portal container reuse - Event listener cleanup - Avoid unnecessary re-renders - Debounce positioning calculations MULTIPLE PORTALS: - Z-index management - Stacking context - Click-through prevention - Focus order between portals REAL-WORLD EXAMPLES: 1. Material-UI Modal 2. Chakra UI Modal 3. React Bootstrap Modal 4. Reach UI Dialog BEST PRACTICES: 1. Always cleanup event listeners 2. Handle SSR (document not available) 3. Support server-side rendering 4. Test with screen readers 5. Support reduced motion ALTERNATIVES: 1. CSS-only modals (limited) 2. iframe (heavy) 3. Third-party libraries ASKED AT: Meta, Google, Microsoft (UI component design)
13. ForwardRef Pattern for Component Libraries
intermediateImplement a TextInput component that: 1. Forwards ref to underlying input element 2. Supports custom styling 3. Exposes focus() and blur() methods 4. Works with form libraries 5. Has proper TypeScript types
// 1. Basic forwardRef implementation
import { forwardRef, useImperativeHandle, useRef } from "react";
const TextInput = forwardRef(function TextInput(props, ref) {
const inputRef = useRef();
// Expose methods to parent via ref
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
blur: () => {
inputRef.current.blur();
},
getValue: () => {
return inputRef.current.value;
},
setValue: (value) => {
inputRef.current.value = value;
},
// Expose the DOM element
element: inputRef.current
}));
return (
<input
ref={inputRef}
{...props}
className={`text-input ${props.className || ""}`}
/>
);
});
// 2. Usage
function Form() {
const inputRef = useRef();
const handleFocus = () => {
inputRef.current?.focus();
};
const handleGetValue = () => {
console.log(inputRef.current?.getValue());
};
return (
<div>
<TextInput
ref={inputRef}
placeholder="Enter text"
onChange={(e) => console.log(e.target.value)}
/>
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleGetValue}>Get Value</button>
</div>
);
}
// 3. With TypeScript
type TextInputProps = React.InputHTMLAttributes<HTMLInputElement> & {
error?: boolean;
variant?: "outlined" | "filled";
};
type TextInputHandle = {
focus: () => void;
blur: () => void;
getValue: () => string;
setValue: (value: string) => void;
element: HTMLInputElement | null;
};
const TextInput = forwardRef<TextInputHandle, TextInputProps>(
function TextInput(props, ref) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
blur: () => inputRef.current?.blur(),
getValue: () => inputRef.current?.value || "",
setValue: (value: string) => {
if (inputRef.current) {
inputRef.current.value = value;
}
},
element: inputRef.current
}));
const className = `text-input ${props.variant || "outlined"} ${
props.error ? "error" : ""
} ${props.className || ""}`;
return (
<input
ref={inputRef}
{...props}
className={className}
/>
);
}
);
// 4. Higher Order Component with forwardRef
function withLogging(WrappedComponent) {
return forwardRef(function WithLogging(props, ref) {
const componentRef = useRef();
// Merge refs
const setRef = useCallback((node) => {
componentRef.current = node;
// Handle ref prop
if (typeof ref === "function") {
ref(node);
} else if (ref) {
ref.current = node;
}
}, [ref]);
useEffect(() => {
console.log("Component mounted:", componentRef.current);
return () => {
console.log("Component unmounted");
};
}, []);
return (
<WrappedComponent
{...props}
ref={setRef}
/>
);
});
}
const TextInputWithLogging = withLogging(TextInput);
// 5. Form integration example
function FormWithRef() {
const formRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
// Access all inputs via ref
const inputs = formRef.current.querySelectorAll("input");
const values = Array.from(inputs).map(input => input.value);
console.log(values);
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<TextInput name="username" required />
<TextInput name="email" type="email" />
<button type="submit">Submit</button>
</form>
);
}Answer: forwardRef passes ref to child component useImperativeHandle exposes custom methods TypeScript provides type safety Works with form libraries
FORWARDREF PATTERN: Pass ref through component to underlying DOM element. USE CASES: 1. Component libraries 2. Form inputs 3. Focus management 4. Animation libraries 5. Third-party integration KEY APIS: 1. FORWARDREF: - Wraps component - Receives ref as second parameter - Passes ref to child element 2. USE IMPERATIVE HANDLE: - Customize ref value - Expose specific methods - Hide implementation details 3. REF MERGING: - Multiple ref consumers - Callback ref pattern - Ref forwarding through HOCs BENEFITS: 1. DOM ACCESS: Parent can call focus(), blur() 2. INTEGRATION: Works with form libraries 3. ENCAPSULATION: Hide implementation details 4. TYPE SAFETY: TypeScript support COMMON PATTERNS: 1. FORM INPUT COMPONENTS: - Expose value methods - Support validation - Integrate with Formik/React Hook Form 2. FOCUS MANAGEMENT: - Programmatic focus - Focus trap - Auto-focus 3. ANIMATION LIBRARIES: - Expose animate() method - Control animations - Integration with Framer Motion PERFORMANCE CONSIDERATIONS: - Avoid inline ref functions - Memoize ref callbacks - Use useCallback for stable refs - Batch ref updates TYPESCRIPT TIPS: 1. Generic forwardRef types 2. Ref type parameter 3. Props type parameter 4. Optional ref methods REAL-WORLD EXAMPLES: 1. Material-UI TextField 2. Chakra UI Input 3. React Select 4. React Datepicker ANTI-PATTERNS: 1. Over-exposing internal methods 2. Breaking component encapsulation 3. Creating ref spaghetti 4. Ignoring accessibility BEST PRACTICES: 1. Only expose necessary methods 2. Document ref API 3. Support both callback and object refs 4. Test ref functionality ASKED AT: Meta, Google, Stripe (component library design)
14. Lazy Loading and Dynamic Imports
intermediateImplement code splitting in a React application: 1. Route-based code splitting 2. Component-based lazy loading 3. Prefetching strategies 4. Loading states and error boundaries 5. Bundle analysis and optimization
// 1. Basic React.lazy usage
import { lazy, Suspense } from "react";
const HeavyComponent = lazy(() => import("./HeavyComponent"));
const AnotherComponent = lazy(() => import("./AnotherComponent"));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// 2. Route-based splitting
import { BrowserRouter, Routes, Route } from "react-router-dom";
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Contact = lazy(() => import("./pages/Contact"));
function RouterApp() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading page...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 3. Named exports with lazy
const UserProfile = lazy(() =>
import("./UserProfile").then(module => ({
default: module.UserProfile
}))
);
// 4. Preloading strategy
function usePreload(moduleImport) {
return useCallback(() => {
moduleImport();
}, [moduleImport]);
}
function Navigation() {
const preloadAbout = usePreload(() => import("./pages/About"));
return (
<nav>
<Link
to="/about"
onMouseEnter={preloadAbout}
onFocus={preloadAbout}
>
About
</Link>
</nav>
);
}
// 5. Bundle analysis with webpack magic comments
const Analytics = lazy(() =>
import(
/* webpackChunkName: "analytics" */
/* webpackPrefetch: true */
/* webpackPreload: true */
"./Analytics"
)
);
// 6. Error boundary for lazy loading
class LazyLoadingErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
retry = () => {
this.setState({ hasError: false, error: null });
// Force remount of lazy component
this.forceUpdate();
};
render() {
if (this.state.hasError) {
return (
<div>
<p>Failed to load component</p>
<button onClick={this.retry}>Retry</button>
</div>
);
}
return this.props.children;
}
}
// 7. Dynamic import with conditions
function DynamicComponent({ componentName }) {
const Component = lazy(() => {
if (componentName === "Chart") {
return import("./ChartComponent");
} else if (componentName === "Table") {
return import("./TableComponent");
}
return import("./DefaultComponent");
});
return (
<Suspense fallback={<div>Loading {componentName}...</div>}>
<Component />
</Suspense>
);
}
// 8. Bundle analysis tool
function BundleAnalyzer() {
useEffect(() => {
if (process.env.NODE_ENV === "development") {
import("webpack-bundle-analyzer").then(({ BundleAnalyzerPlugin }) => {
// Configure analyzer
});
}
}, []);
return null;
}Answer: React.lazy for component code splitting Suspense for loading states Route-based splitting with React Router Prefetching for better UX
CODE SPLITTING PATTERNS: Split bundle into smaller chunks loaded on demand. BENEFITS: 1. FASTER INITIAL LOAD: Smaller initial bundle 2. BETTER PERFORMANCE: Load only needed code 3. CACHE EFFICIENCY: Independent chunk caching 4. BANDWIDTH SAVINGS: Avoid unused code download SPLITTING STRATEGIES: 1. ROUTE-BASED: - Split by page/route - Natural user journey boundaries - Easy to implement 2. COMPONENT-BASED: - Split heavy components - Modal/dialog content - Below-the-fold content 3. VENDOR SPLITTING: - Separate third-party libraries - Stable chunk hashes - Better caching 4. DYNAMIC IMPORTS: - Load on user interaction - Conditional loading - Feature flags PERFORMANCE OPTIMIZATIONS: 1. PREFETCHING: - Load during idle time - Mouse hover/focus events - webpackPrefetch comment 2. PRELOADING: - High priority loading - Critical resources - webpackPreload comment 3. BUNDLE ANALYSIS: - webpack-bundle-analyzer - Source map exploration - Duplicate detection IMPLEMENTATION DETAILS: 1. REACT.LAZY: - Works with default exports - Requires Suspense boundary - Error boundary for failures 2. SUSPENSE: - Shows fallback during loading - Can be nested - Supports concurrent features 3. WEBPACK MAGIC COMMENTS: - webpackChunkName: Custom chunk names - webpackPrefetch: Idle time loading - webpackPreload: High priority loading ERROR HANDLING: 1. Network failures 2. Module not found 3. Loading timeouts 4. Retry mechanisms MEASUREMENT METRICS: 1. First Contentful Paint 2. Time to Interactive 3. Bundle size reduction 4. Cache hit ratio BEST PRACTICES: 1. Split at route boundaries 2. Use meaningful chunk names 3. Implement loading states 4. Add error boundaries 5. Monitor bundle sizes TOOLS: 1. Webpack Bundle Analyzer 2. Source Map Explorer 3. Lighthouse 4. Webpack performance hints ASKED AT: Meta, Google, Netflix (performance optimization)
15. Container/Presentational Components
intermediateImplement the Container/Presentational pattern: 1. Container handles data/logic 2. Presentational handles UI rendering 3. Clear separation of concerns 4. Testable components 5. Reusable presentational components
// 1. Presentational Component (Dumb/Stateless)
function UserListPresentation({ users, isLoading, error, onUserClick }) {
if (error) {
return <div className="error">Error: {error.message}</div>;
}
if (isLoading) {
return <div className="loading">Loading users...</div>;
}
return (
<ul className="user-list">
{users.map(user => (
<li
key={user.id}
className="user-item"
onClick={() => onUserClick(user.id)}
>
<img src={user.avatar} alt={user.name} />
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
</li>
))}
</ul>
);
}
// 2. Container Component (Smart/Stateful)
function UserListContainer() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch("/api/users");
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
};
fetchUsers();
}, []);
const handleUserClick = useCallback((userId) => {
console.log("User clicked:", userId);
// Navigate or show details
}, []);
return (
<UserListPresentation
users={users}
isLoading={isLoading}
error={error}
onUserClick={handleUserClick}
/>
);
}
// 3. Hooks-based Container (Modern approach)
function useUsers() {
const [state, setState] = useState({
users: [],
isLoading: true,
error: null
});
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
const response = await fetch("/api/users");
const users = await response.json();
setState({ users, isLoading: false, error: null });
} catch (error) {
setState({ users: [], isLoading: false, error });
}
};
const updateUser = useCallback((userId, updates) => {
setState(prev => ({
...prev,
users: prev.users.map(user =>
user.id === userId ? { ...user, ...updates } : user
)
}));
}, []);
return {
...state,
fetchUsers,
updateUser
};
}
// Container using custom hook
function UserListContainerWithHook() {
const { users, isLoading, error, updateUser } = useUsers();
return (
<UserListPresentation
users={users}
isLoading={isLoading}
error={error}
onUserClick={(userId) => {
updateUser(userId, { selected: true });
}}
/>
);
}
// 4. Higher Order Component Container
function withUsers(WrappedComponent) {
return function WithUsers(props) {
const usersData = useUsers();
return (
<WrappedComponent
{...props}
{...usersData}
/>
);
};
}
// Enhanced component
const UserListWithUsers = withUsers(UserListPresentation);
// 5. Render Props Container
function UsersContainer({ children }) {
const usersData = useUsers();
return children(usersData);
}
// Usage
function App() {
return (
<UsersContainer>
{({ users, isLoading, error }) => (
<UserListPresentation
users={users}
isLoading={isLoading}
error={error}
/>
)}
</UsersContainer>
);
}
// 6. Testing presentational component
// Easy to test with mock props
test("UserListPresentation renders users", () => {
const mockUsers = [
{ id: 1, name: "John", email: "john@example.com", avatar: "" }
];
render(
<UserListPresentation
users={mockUsers}
isLoading={false}
error={null}
onUserClick={jest.fn()}
/>
);
expect(screen.getByText("John")).toBeInTheDocument();
});Answer: Container handles data/logic, Presentational handles UI Clear separation of concerns Custom hooks for reusable logic Easy testing of presentational components
CONTAINER/PRESENTATIONAL PATTERN: Separation of data/logic from UI rendering. BENEFITS: 1. SEPARATION OF CONCERNS: Clear responsibilities 2. REUSABILITY: Presentational components reusable 3. TESTABILITY: Easy to test in isolation 4. MAINTAINABILITY: Changes isolated to one layer MODERN EVOLUTION: 1. HOOKS: Replace container components 2. CUSTOM HOOKS: Extract logic 3. RENDER PROPS: Flexible composition 4. HOC: Reuse logic across components PRESENTATIONAL COMPONENTS: - Receive data via props - No internal state (usually) - No side effects - Pure rendering - Styled components CONTAINER COMPONENTS: - Manage state - Handle side effects - Business logic - Data fetching - Pass data to presentational IMPLEMENTATION PATTERNS: 1. CLASSIC: - Container class component - Presentational functional component 2. HOOKS-BASED: - Custom hook for logic - Functional container - Same presentational component 3. HOC: - Higher Order Component wrapper - Inject props - Reusable across components 4. RENDER PROPS: - Container provides data - Children function renders - Maximum flexibility TESTING STRATEGIES: 1. PRESENTATIONAL: - Test with mock props - Snapshot testing - User interaction tests 2. CONTAINER: - Mock API calls - State transitions - Side effect testing PERFORMANCE: - Presentational components can be memoized - Container re-renders on data changes - Optimize with React.memo REAL-WORLD USAGE: 1. FORM CONTAINERS: Handle form state 2. DATA FETCHING: API integration 3. AUTHENTICATION: User state management 4. THEMING: Theme propagation BEST PRACTICES: 1. Keep presentational components pure 2. Use TypeScript for prop types 3. Document component contracts 4. Test both layers independently ALTERNATIVE PATTERNS: 1. HEADLESS UI: Logic without styling 2. COMPOUND COMPONENTS: Shared state 3. PROVIDER PATTERN: Context-based ASKED AT: Meta, Google, Amazon (architecture design)
16. Dependency Injection for Testing
advancedImplement dependency injection in React for: 1. Mocking API clients in tests 2. Switching implementations (real vs mock) 3. Service layer abstraction 4. Testing components in isolation
// 1. Service interface
class ApiService {
async getUsers() {
throw new Error("Not implemented");
}
async createUser(userData) {
throw new Error("Not implemented");
}
}
// 2. Real implementation
class RealApiService extends ApiService {
async getUsers() {
const response = await fetch("/api/users");
return response.json();
}
async createUser(userData) {
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData)
});
return response.json();
}
}
// 3. Mock implementation for testing
class MockApiService extends ApiService {
constructor() {
super();
this.users = [
{ id: 1, name: "Test User", email: "test@example.com" }
];
}
async getUsers() {
return this.users;
}
async createUser(userData) {
const newUser = { id: Date.now(), ...userData };
this.users.push(newUser);
return newUser;
}
}
// 4. Context for dependency injection
const ApiServiceContext = createContext(new RealApiService());
function ApiServiceProvider({ children, service }) {
return (
<ApiServiceContext.Provider value={service}>
{children}
</ApiServiceContext.Provider>
);
}
// 5. Hook to use service
function useApiService() {
return useContext(ApiServiceContext);
}
// 6. Component using injected service
function UserList() {
const [users, setUsers] = useState([]);
const apiService = useApiService();
useEffect(() => {
apiService.getUsers().then(setUsers);
}, [apiService]);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 7. App with real service
function App() {
return (
<ApiServiceProvider service={new RealApiService()}>
<UserList />
</ApiServiceProvider>
);
}
// 8. Testing with mock service
describe("UserList", () => {
it("renders users from mock service", async () => {
const mockService = new MockApiService();
render(
<ApiServiceProvider service={mockService}>
<UserList />
</ApiServiceProvider>
);
// Wait for users to load
await screen.findByText("Test User");
expect(screen.getByText("Test User")).toBeInTheDocument();
});
});
// 9. Factory pattern for service creation
function createApiService(config) {
if (config.useMock) {
return new MockApiService();
}
if (config.env === "test") {
return new MockApiService();
}
return new RealApiService();
}
// 10. Parameter injection alternative
function UserList({ apiService = new RealApiService() }) {
const [users, setUsers] = useState([]);
useEffect(() => {
apiService.getUsers().then(setUsers);
}, [apiService]);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 11. Higher Order Component injection
function withApiService(WrappedComponent) {
return function WithApiService(props) {
const apiService = useApiService();
return (
<WrappedComponent
{...props}
apiService={apiService}
/>
);
};
}
const UserListWithService = withApiService(UserList);Answer: Dependency injection via Context API Service interfaces with real/mock implementations Testing with mocked services Factory pattern for environment-based creation
DEPENDENCY INJECTION IN REACT: Provide dependencies from outside rather than hardcoding. BENEFITS: 1. TESTABILITY: Easy mocking 2. FLEXIBILITY: Switch implementations 3. MAINTAINABILITY: Decoupled code 4. CONFIGURATION: Environment-based setups IMPLEMENTATION PATTERNS: 1. CONTEXT API: - Service provider component - Context for service access - Default implementation 2. PROP INJECTION: - Pass service as prop - Default parameter values - Explicit dependencies 3. HIGHER ORDER COMPONENTS: - Wrap with service injection - Reuse across components - TypeScript support 4. CUSTOM HOOKS: - useService() hook - Service access abstraction - Testing hooks SERVICE DESIGN: 1. INTERFACE/ABSTRACT CLASS: - Define contract - Multiple implementations - TypeScript interfaces 2. REAL IMPLEMENTATION: - Production code - API integration - Error handling 3. MOCK IMPLEMENTATION: - Test data - Simulated responses - Controlled behavior TESTING STRATEGIES: 1. UNIT TESTS: - Mock services - Isolated components - Fast execution 2. INTEGRATION TESTS: - Real services - End-to-end flows - Environment setup 3. E2E TESTS: - Production setup - User scenarios - Browser testing CONFIGURATION PATTERNS: 1. ENVIRONMENT VARIABLES: - NODE_ENV based - Feature flags - Service URLs 2. FACTORY PATTERN: - Create based on config - Singleton services - Cached instances 3. LAZY LOADING: - Dynamic imports - Code splitting - Bundle optimization PERFORMANCE CONSIDERATIONS: - Service instance creation - Context re-renders - Memoization of services - Singleton pattern REAL-WORLD USE CASES: 1. API CLIENT: Axios vs Fetch 2. AUTHENTICATION: Real vs mock auth 3. ANALYTICS: Production vs dev 4. STORAGE: localStorage vs IndexedDB BEST PRACTICES: 1. Define clear interfaces 2. Use TypeScript 3. Default to real implementation 4. Easy testing setup 5. Document service contracts ASKED AT: Meta, Google, Microsoft (testing architecture)
17. Factory Pattern for Dynamic Components
advancedImplement a component factory that: 1. Creates components based on configuration 2. Handles dynamic component types 3. Supports plugins/extensions 4. Type-safe with TypeScript 5. Configurable via JSON
// 1. Component registry
const componentRegistry = {};
function registerComponent(type, component) {
componentRegistry[type] = component;
}
function getComponent(type) {
const component = componentRegistry[type];
if (!component) {
throw new Error(`Component type "${type}" not registered`);
}
return component;
}
// 2. Component factory
function ComponentFactory({ type, props, children }) {
const Component = getComponent(type);
return (
<Component {...props}>
{children}
</Component>
);
}
// 3. Example components
const Button = ({ label, onClick, variant = "primary" }) => (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{label}
</button>
);
const Input = ({ label, value, onChange, type = "text" }) => (
<div className="form-group">
<label>{label}</label>
<input
type={type}
value={value}
onChange={onChange}
className="form-control"
/>
</div>
);
const Card = ({ title, children }) => (
<div className="card">
{title && <div className="card-header">{title}</div>}
<div className="card-body">{children}</div>
</div>
);
// 4. Register components
registerComponent("button", Button);
registerComponent("input", Input);
registerComponent("card", Card);
// 5. Dynamic form from configuration
const formConfig = [
{ type: "card", props: { title: "User Registration" },
children: [
{
type: "input",
props: {
label: "Name",
name: "name",
required: true
}
},
{
type: "input",
props: {
label: "Email",
name: "email",
type: "email"
}
},
{
type: "button",
props: {
label: "Submit",
variant: "primary",
onClick: () => console.log("Submitted")
}
}
]
}
];
function DynamicForm({ config }) {
const renderComponent = (item, index) => {
const { type, props, children } = item;
return (
<ComponentFactory
key={index}
type={type}
props={props}
>
{children && children.map(renderComponent)}
</ComponentFactory>
);
};
return config.map(renderComponent);
}
// 6. TypeScript support
type ComponentType = "button" | "input" | "card" | string;
interface ComponentConfig {
type: ComponentType;
props: Record<string, any>;
children?: ComponentConfig[];
}
interface ComponentFactoryProps {
type: ComponentType;
props: Record<string, any>;
children?: React.ReactNode;
}
// 7. Plugin system
function registerPlugin(plugin) {
plugin.components.forEach(({ type, component }) => {
registerComponent(type, component);
});
}
const chartPlugin = {
components: [
{ type: "lineChart", component: LineChart },
{ type: "barChart", component: BarChart }
]
};
registerPlugin(chartPlugin);
// 8. Dynamic component loader
async function loadComponent(type) {
// Dynamic import based on type
switch (type) {
case "richTextEditor":
return import("./RichTextEditor").then(m => m.default);
case "dataGrid":
return import("./DataGrid").then(m => m.default);
default:
return getComponent(type);
}
}
function AsyncComponentFactory({ type, props }) {
const [Component, setComponent] = useState(null);
useEffect(() => {
loadComponent(type).then(setComponent);
}, [type]);
if (!Component) {
return <div>Loading {type}...</div>;
}
return <Component {...props} />;
}
// 9. Usage
function App() {
return (
<div>
<DynamicForm config={formConfig} />
{/* Direct factory usage */}
<ComponentFactory
type="card"
props={{ title: "Manual Card" }}
>
<p>This content is manually passed.</p>
<ComponentFactory
type="button"
props={{ label: "Click me", onClick: () => alert("Clicked") }}
/>
</ComponentFactory>
</div>
);
}Answer: Component registry maps types to components Factory creates components based on type Dynamic forms from JSON configuration Plugin system for extensibility
FACTORY PATTERN FOR COMPONENTS: Create objects without specifying exact class. USE CASES: 1. CMS/BUILDER SYSTEMS: Drag-and-drop builders 2. FORM GENERATORS: Dynamic forms from JSON 3. PLUGIN ARCHITECTURES: Extensible systems 4. CONFIGURATION-DRIVEN UIS: JSON-driven interfaces IMPLEMENTATION PATTERNS: 1. REGISTRY PATTERN: - Central component registry - Type-to-component mapping - Runtime registration 2. FACTORY COMPONENT: - Looks up component by type - Passes props and children - Error handling for missing types 3. CONFIGURATION DRIVEN: - JSON configuration - Nested component trees - Dynamic rendering 4. PLUGIN SYSTEM: - External component registration - Lazy loading - Version compatibility ADVANCED FEATURES: 1. ASYNC LOADING: - Dynamic imports - Code splitting - Loading states 2. VALIDATION: - Prop validation - Required fields - Type checking 3. THEMING: - Style injection - Theme propagation - Component variants 4. STATE MANAGEMENT: - Form state - Validation state - Submission handling PERFORMANCE OPTIMIZATIONS: - Memoize factory components - Lazy load heavy components - Cache component instances - Virtualize lists TYPE SAFETY: 1. TypeScript generics 2. Component type unions 3. Prop type validation 4. Configuration schema validation REAL-WORLD EXAMPLES: 1. FORM.IO: Dynamic form builder 2. STORYBOOK: Component explorer 3. RETOOL: Internal tool builder 4. SANITY: Content studio SECURITY CONSIDERATIONS: 1. Sanitize configuration 2. Validate component types 3. Limit accessible components 4. Sandbox dynamic code BEST PRACTICES: 1. Default error component 2. Component versioning 3. Documentation generation 4. Test configuration files ASKED AT: Meta, Google, Microsoft (platform engineering)
18. Provider Pattern with Multiple Contexts
intermediateImplement a multi-context provider pattern: 1. Theme provider with light/dark mode 2. Auth provider with user state 3. Notification provider with toasts 4. Combined provider for clean app setup 5. Custom hooks for each context
// 1. Theme Context
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = useCallback(() => {
setTheme(prev => prev === "light" ? "dark" : "light");
}, []);
const value = useMemo(() => ({
theme,
toggleTheme,
isDark: theme === "dark"
}), [theme, toggleTheme]);
return (
<ThemeContext.Provider value={value}>
<div className={`theme-${theme}`}>
{children}
</div>
</ThemeContext.Provider>
);
}
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
}
// 2. Auth Context
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check stored auth
const storedUser = localStorage.getItem("user");
if (storedUser) {
setUser(JSON.parse(storedUser));
}
setLoading(false);
}, []);
const login = useCallback(async (credentials) => {
// API call
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify(credentials)
});
const userData = await response.json();
setUser(userData);
localStorage.setItem("user", JSON.stringify(userData));
}, []);
const logout = useCallback(() => {
setUser(null);
localStorage.removeItem("user");
}, []);
const value = useMemo(() => ({
user,
loading,
login,
logout,
isAuthenticated: !!user
}), [user, loading, login, logout]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within AuthProvider");
}
return context;
}
// 3. Notification Context
const NotificationContext = createContext();
function NotificationProvider({ children }) {
const [notifications, setNotifications] = useState([]);
const addNotification = useCallback((notification) => {
const id = Date.now();
const newNotification = { id, ...notification };
setNotifications(prev => [...prev, newNotification]);
// Auto remove after 5 seconds
setTimeout(() => {
removeNotification(id);
}, 5000);
}, []);
const removeNotification = useCallback((id) => {
setNotifications(prev => prev.filter(n => n.id !== id));
}, []);
const value = useMemo(() => ({
notifications,
addNotification,
removeNotification,
success: (message) => addNotification({ type: "success", message }),
error: (message) => addNotification({ type: "error", message }),
info: (message) => addNotification({ type: "info", message })
}), [notifications, addNotification, removeNotification]);
return (
<NotificationContext.Provider value={value}>
{children}
<NotificationList />
</NotificationContext.Provider>
);
}
function NotificationList() {
const { notifications, removeNotification } = useNotification();
return (
<div className="notifications">
{notifications.map(notification => (
<div
key={notification.id}
className={`notification notification-${notification.type}`}
>
{notification.message}
<button onClick={() => removeNotification(notification.id)}>
×
</button>
</div>
))}
</div>
);
}
function useNotification() {
const context = useContext(NotificationContext);
if (!context) {
throw new Error("useNotification must be used within NotificationProvider");
}
return context;
}
// 4. Combined Provider
function AppProvider({ children }) {
return (
<ThemeProvider>
<AuthProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</AuthProvider>
</ThemeProvider>
);
}
// 5. Usage in components
function UserProfile() {
const { user, logout } = useAuth();
const { theme, toggleTheme } = useTheme();
const { success } = useNotification();
const handleLogout = () => {
logout();
success("Logged out successfully");
};
return (
<div className="profile">
<h2>Welcome, {user.name}</h2>
<button onClick={handleLogout}>Logout</button>
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "Dark" : "Light"} Mode
</button>
</div>
);
}
// 6. App setup
function App() {
return (
<AppProvider>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<UserProfile />} />
</Routes>
</Router>
</AppProvider>
);
}
// 7. TypeScript version
type Theme = "light" | "dark";
interface ThemeContextValue {
theme: Theme;
toggleTheme: () => void;
isDark: boolean;
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
// 8. Performance optimized provider
function OptimizedProvider({ children }) {
// Split contexts to avoid unnecessary re-renders
return (
<ThemeProvider>
<AuthStateProvider>
<AuthActionsProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</AuthActionsProvider>
</AuthStateProvider>
</ThemeProvider>
);
}Answer: Multiple providers for theme, auth, notifications Combined AppProvider for clean setup Custom hooks for each context TypeScript support for type safety
PROVIDER PATTERN: Component that provides data/functionality to descendants via Context. BENEFITS: 1. GLOBAL STATE: Accessible anywhere in tree 2. PROP DRILLING SOLUTION: Avoid passing through many levels 3. REUSABLE LOGIC: Encapsulate complex logic 4. TESTABILITY: Easy to mock providers COMMON PROVIDERS: 1. THEME PROVIDER: - Color schemes - Typography - Spacing 2. AUTH PROVIDER: - User authentication - Login/logout - Protected routes 3. NOTIFICATION PROVIDER: - Toast messages - Alerts - Progress indicators 4. DATA PROVIDER: - API caching - Data fetching - State management IMPLEMENTATION PATTERNS: 1. SINGLE PROVIDER: - One context - Simple state - Small apps 2. MULTIPLE PROVIDERS: - Separate concerns - Nested providers - Combined provider component 3. SPLIT PROVIDERS: - State vs actions - Optimize re-renders - Fine-grained updates PERFORMANCE OPTIMIZATIONS: 1. MEMOIZATION: - useMemo for context value - useCallback for functions - Prevent unnecessary re-renders 2. CONTEXT SPLITTING: - Separate state from updaters - Static vs dynamic values - Reduce consumer re-renders 3. SELECTOR PATTERN: - Custom hooks with selectors - Subscribe to specific values - Avoid full context re-renders TESTING STRATEGIES: 1. PROVIDER MOCKING: - Mock context values - Test components in isolation - Provider test wrappers 2. INTEGRATION TESTS: - Real providers - User flows - State transitions 3. E2E TESTS: - Full app with providers - Authentication flows - Theme switching BEST PRACTICES: 1. Always provide default values 2. Create custom hooks for consumption 3. Split large providers 4. Document provider contracts 5. Use TypeScript REAL-WORLD EXAMPLES: 1. REACT ROUTER: Router provider 2. REDUX: Provider component 3. APOLLO CLIENT: ApolloProvider 4. MATERIAL-UI: ThemeProvider ASKED AT: Meta, Google, Amazon (application architecture)
19. Composing Custom Hooks
intermediateCreate and compose custom hooks: 1. useLocalStorage hook 2. useFetch hook 3. useDebounce hook 4. Compose them for complex logic 5. TypeScript support
// 1. useLocalStorage hook
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = useCallback((value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
}, [key, storedValue]);
const removeValue = useCallback(() => {
try {
setStoredValue(initialValue);
window.localStorage.removeItem(key);
} catch (error) {
console.error(error);
}
}, [key, initialValue]);
return [storedValue, setValue, removeValue];
}
// 2. useFetch hook (simplified)
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== "AbortError") {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, [url, JSON.stringify(options)]);
return { data, loading, error };
}
// 3. useDebounce hook
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
// 4. Composed hook: useLocalStorageFetch
function useLocalStorageFetch(key, url) {
// Get cached data from localStorage
const [cachedData, setCachedData, removeCachedData] = useLocalStorage(key, null);
// Fetch fresh data
const { data: freshData, loading, error } = useFetch(url);
// Update cache when fresh data arrives
useEffect(() => {
if (freshData) {
setCachedData(freshData);
}
}, [freshData, setCachedData]);
// Determine which data to show
const data = freshData || cachedData;
// Clear cache
const clearCache = useCallback(() => {
removeCachedData();
}, [removeCachedData]);
return {
data,
loading,
error,
clearCache,
isCached: !!cachedData && !freshData
};
}
// 5. Composed hook: useDebouncedSearch
function useDebouncedSearch(initialQuery = "", delay = 300) {
const [query, setQuery] = useState(initialQuery);
const debouncedQuery = useDebounce(query, delay);
const { data: results, loading, error } = useFetch(
debouncedQuery ? `/api/search?q=${debouncedQuery}` : null
);
return {
query,
setQuery,
debouncedQuery,
results,
loading,
error
};
}
// 6. Composed hook: useForm with validation and localStorage
function useForm(initialState, options = {}) {
const { validate, persistKey } = options;
// Get persisted form data or use initial
const [persistedData, setPersistedData, clearPersistedData] = useLocalStorage(
persistKey,
initialState
);
const [formData, setFormData] = useState(persistedData);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
// Update localStorage on change
useEffect(() => {
if (persistKey) {
setPersistedData(formData);
}
}, [formData, persistKey, setPersistedData]);
const handleChange = useCallback((field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
// Clear error when user starts typing
setErrors(prev => ({
...prev,
[field]: undefined
}));
}, []);
const handleBlur = useCallback((field) => {
setTouched(prev => ({
...prev,
[field]: true
}));
// Validate on blur
if (validate) {
const fieldErrors = validate(field, formData[field], formData);
if (fieldErrors) {
setErrors(prev => ({
...prev,
[field]: fieldErrors
}));
}
}
}, [validate, formData]);
const resetForm = useCallback(() => {
setFormData(initialState);
setErrors({});
setTouched({});
if (persistKey) {
clearPersistedData();
}
}, [initialState, persistKey, clearPersistedData]);
return {
formData,
handleChange,
handleBlur,
errors,
touched,
resetForm,
setFormData
};
}
// 7. Usage example
function SearchComponent() {
const {
query,
setQuery,
results,
loading,
error
} = useDebouncedSearch();
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{loading && <div>Loading...</div>}
{error && <div>Error: {error.message}</div>}
<ul>
{results?.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
// 8. TypeScript generic hooks
type UseLocalStorageReturn<T> = [T, (value: T | ((val: T) => T)) => void, () => void];
function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageReturn<T> {
// Implementation with types
}Answer: Custom hooks for localStorage, fetching, debouncing Composed hooks combine multiple hooks TypeScript generics for type safety Reusable logic extraction
HOOKS COMPOSITION PATTERN: Combine multiple hooks to create complex behavior. BENEFITS: 1. REUSABILITY: Logic shared across components 2. COMPOSABILITY: Combine simple hooks 3. TESTABILITY: Isolated logic testing 4. MAINTAINABILITY: Centralized logic COMMON HOOKS TO CREATE: 1. STATE MANAGEMENT: - useLocalStorage - useSessionStorage - usePrevious 2. DATA FETCHING: - useFetch - useQuery (GraphQL) - useMutation 3. UI/UX: - useDebounce - useThrottle - useMediaQuery - useClickOutside 4. FORMS: - useForm - useField - useValidation 5. PERFORMANCE: - useMemoCompare - useWhyDidYouUpdate - useRenderCount COMPOSITION PATTERNS: 1. SEQUENTIAL COMPOSITION: - Hook results used in next hook - Example: useDebounce → useFetch 2. PARALLEL COMPOSITION: - Multiple independent hooks - Combine results at the end 3. NESTED COMPOSITION: - Hooks within custom hooks - Shared state between hooks PERFORMANCE CONSIDERATIONS: - Memoize hook results - Avoid unnecessary re-renders - Cleanup side effects - Dependency array optimization TESTING STRATEGIES: 1. UNIT TESTS: - Test hooks in isolation - Mock dependencies - Test return values 2. INTEGRATION TESTS: - Test composed hooks - Real dependencies - Interaction testing 3. E2E TESTS: - Full user flows - Hook integration BEST PRACTICES: 1. Name hooks with "use" prefix 2. Return consistent interface 3. Handle errors gracefully 4. Provide cleanup functions 5. Document hook API TYPE SAFETY: 1. TypeScript generics 2. Proper return types 3. Input validation 4. Error types REAL-WORLD LIBRARIES: 1. REACT USE: Collection of hooks 2. USE-HOOKS: Common patterns 3. SWR/REACT QUERY: Data fetching 4. FORMIK/REACT HOOK FORM: Form handling ASKED AT: Meta, Google, Netflix (custom hooks)
20. State Machines for Complex UI Logic
advancedImplement a fetch machine with XState: 1. Define states: idle, loading, success, error 2. Transitions between states 3. Side effects (fetching) 4. React integration 5. TypeScript support
import { createMachine, assign } from "xstate";
import { useMachine } from "@xstate/react";
// 1. Define the machine
const fetchMachine = createMachine({
id: "fetch",
initial: "idle",
context: {
data: null,
error: null
},
states: {
idle: {
on: {
FETCH: "loading"
}
},
loading: {
invoke: {
src: "fetchData",
onDone: {
target: "success",
actions: assign({
data: (_, event) => event.data
})
},
onError: {
target: "error",
actions: assign({
error: (_, event) => event.data
})
}
},
on: {
CANCEL: "idle"
}
},
success: {
on: {
REFETCH: "loading",
RESET: {
target: "idle",
actions: assign({
data: null,
error: null
})
}
}
},
error: {
on: {
RETRY: "loading",
RESET: {
target: "idle",
actions: assign({
data: null,
error: null
})
}
}
}
}
});
// 2. Services (side effects)
const services = {
fetchData: async (context, event) => {
const response = await fetch(event.url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
};
// 3. React component using the machine
function FetchComponent({ url }) {
const [state, send] = useMachine(fetchMachine, {
services
});
const { data, error } = state.context;
const handleFetch = () => {
send({ type: "FETCH", url });
};
const handleCancel = () => {
send({ type: "CANCEL" });
};
const handleRetry = () => {
send({ type: "RETRY", url });
};
const handleReset = () => {
send({ type: "RESET" });
};
return (
<div>
<div>Current state: {state.value}</div>
{state.matches("idle") && (
<button onClick={handleFetch}>
Fetch Data
</button>
)}
{state.matches("loading") && (
<div>
<div>Loading...</div>
<button onClick={handleCancel}>Cancel</button>
</div>
)}
{state.matches("success") && (
<div>
<h3>Data loaded successfully</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={handleFetch}>Refetch</button>
<button onClick={handleReset}>Reset</button>
</div>
)}
{state.matches("error") && (
<div>
<h3>Error: {error?.message}</h3>
<button onClick={handleRetry}>Retry</button>
<button onClick={handleReset}>Reset</button>
</div>
)}
</div>
);
}
// 4. TypeScript version
interface FetchContext {
data: any;
error: Error | null;
}
type FetchEvent =
| { type: "FETCH"; url: string }
| { type: "CANCEL" }
| { type: "RETRY"; url: string }
| { type: "RESET" }
| { type: "REFETCH"; url: string };
type FetchState =
| { value: "idle"; context: FetchContext }
| { value: "loading"; context: FetchContext }
| { value: "success"; context: FetchContext & { data: any } }
| { value: "error"; context: FetchContext & { error: Error } };
// 5. Form machine example
const formMachine = createMachine({
id: "form",
initial: "editing",
context: {
values: {},
errors: {},
touched: {}
},
states: {
editing: {
on: {
CHANGE: {
actions: assign({
values: (context, event) => ({
...context.values,
[event.field]: event.value
})
})
},
BLUR: {
actions: assign({
touched: (context, event) => ({
...context.touched,
[event.field]: true
})
})
},
SUBMIT: {
target: "validating",
cond: "hasChanges"
}
}
},
validating: {
invoke: {
src: "validateForm",
onDone: [
{
target: "submitting",
cond: (_, event) => event.data.isValid
},
{
target: "editing",
actions: assign({
errors: (_, event) => event.data.errors
})
}
]
}
},
submitting: {
invoke: {
src: "submitForm",
onDone: "success",
onError: {
target: "editing",
actions: assign({
errors: (_, event) => ({ submit: event.data.message })
})
}
}
},
success: {
type: "final"
}
}
});
// 6. Parallel states
const multiStepFormMachine = createMachine({
id: "multiStepForm",
type: "parallel",
states: {
step1: {
initial: "idle",
states: {
idle: { on: { NEXT: "completed" } },
completed: { type: "final" }
}
},
step2: {
initial: "idle",
states: {
idle: { on: { NEXT: "completed" } },
completed: { type: "final" }
}
},
step3: {
initial: "idle",
states: {
idle: {
on: {
NEXT: "completed",
SKIP: "skipped"
}
},
completed: { type: "final" },
skipped: { type: "final" }
}
}
},
on: {
RESET: {
target: [".step1.idle", ".step2.idle", ".step3.idle"]
}
}
});Answer: XState machines define states and transitions React integration with useMachine TypeScript for type safety Parallel states for complex workflows
STATE MACHINES PATTERN: Explicitly model application states and transitions. BENEFITS: 1. PREDICTABLE: All states defined 2. VISUALIZABLE: Can generate statecharts 3. TESTABLE: Easy to test transitions 4. MAINTAINABLE: Clear state logic KEY CONCEPTS: 1. STATES: - Finite set of states - Nested/parallel states - Initial/final states 2. TRANSITIONS: - Events trigger transitions - Guards for conditions - Actions on transitions 3. CONTEXT: - Extended state - Data associated with machine - Can be updated 4. SERVICES: - Side effects - Promises/observables - Callbacks REACT INTEGRATION: 1. USE MACHINE: - Hook to use machine - Returns [state, send] - Service management 2. ACTORS: - Spawn child machines - Communication between machines - Complex workflows USE CASES: 1. DATA FETCHING: - Loading states - Error handling - Retry logic 2. FORMS: - Validation states - Submission flow - Multi-step forms 3. UI COMPONENTS: - Modal/dialog states - Accordion/tabs - Wizard flows 4. AUTHENTICATION: - Login/logout flow - Session management - Permission states PERFORMANCE: - Machines are lightweight - Minimal re-renders - Efficient state updates - Memory efficient TOOLING: 1. XSTATE VISUALIZER: Visualize machines 2. XSTATE INSPECT: Debug machines 3. TYPE GENERATION: TypeScript support 4. TESTING: Built-in test utilities ALTERNATIVES: 1. USE REDUCER: Simple state machines 2. ZUSTAND: Simple state management 3. REDUX SAGA: Complex side effects 4. MOBX STATE TREE: Tree-based state BEST PRACTICES: 1. Start with simple machines 2. Use TypeScript 3. Keep machines focused 4. Test all transitions 5. Visualize complex machines ASKED AT: Meta, Microsoft, Netflix (complex UI logic)
21. Optimistic Updates Pattern
advancedImplement optimistic updates for a todo list: 1. Show immediate UI update 2. Send async request 3. Rollback on error 4. Retry failed updates 5. Handle race conditions
// 1. Custom hook for optimistic updates
function useOptimisticUpdate(initialData, updateFn, options = {}) {
const [data, setData] = useState(initialData);
const [optimisticUpdates, setOptimisticUpdates] = useState(new Map());
const [errors, setErrors] = useState(new Map());
const applyUpdate = useCallback(async (id, update) => {
// Generate optimistic ID
const optimisticId = `opt_${Date.now()}`;
// 1. Apply optimistic update immediately
setData(prev => {
const newData = [...prev];
const index = newData.findIndex(item => item.id === id);
if (index !== -1) {
newData[index] = {
...newData[index],
...update,
_optimisticId: optimisticId,
_isOptimistic: true
};
}
return newData;
});
// Store optimistic update
setOptimisticUpdates(prev =>
new Map(prev).set(optimisticId, { id, update })
);
try {
// 2. Send actual API request
const result = await updateFn(id, update);
// 3. Replace optimistic item with real data
setData(prev => {
const newData = [...prev];
const index = newData.findIndex(
item => item._optimisticId === optimisticId
);
if (index !== -1) {
newData[index] = {
...result,
_optimisticId: undefined,
_isOptimistic: false
};
}
return newData;
});
// Remove from optimistic updates
setOptimisticUpdates(prev => {
const next = new Map(prev);
next.delete(optimisticId);
return next;
});
return { success: true, data: result };
} catch (error) {
// 4. Rollback on error
setData(prev => {
const newData = [...prev];
const index = newData.findIndex(
item => item._optimisticId === optimisticId
);
if (index !== -1) {
// Remove the optimistic item
newData.splice(index, 1);
}
return newData;
});
// Store error
setErrors(prev =>
new Map(prev).set(optimisticId, { error, id, update })
);
// Remove from optimistic updates
setOptimisticUpdates(prev => {
const next = new Map(prev);
next.delete(optimisticId);
return next;
});
return { success: false, error };
}
}, [updateFn]);
const retryUpdate = useCallback(async (optimisticId) => {
const failedUpdate = errors.get(optimisticId);
if (!failedUpdate) return;
// Remove from errors
setErrors(prev => {
const next = new Map(prev);
next.delete(optimisticId);
return next;
});
// Retry the update
return applyUpdate(failedUpdate.id, failedUpdate.update);
}, [errors, applyUpdate]);
const retryAll = useCallback(async () => {
const results = [];
for (const [optimisticId] of errors) {
const result = await retryUpdate(optimisticId);
results.push(result);
}
return results;
}, [errors, retryUpdate]);
return {
data,
applyUpdate,
retryUpdate,
retryAll,
optimisticCount: optimisticUpdates.size,
errorCount: errors.size,
hasErrors: errors.size > 0
};
}
// 2. Todo list component using optimistic updates
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React", completed: false }
]);
const api = {
updateTodo: async (id, updates) => {
const response = await fetch(`/api/todos/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates)
});
return response.json();
}
};
const {
data: optimisticTodos,
applyUpdate,
retryAll,
hasErrors
} = useOptimisticUpdate(todos, api.updateTodo);
const toggleTodo = async (id) => {
const todo = optimisticTodos.find(t => t.id === id);
if (!todo) return;
await applyUpdate(id, { completed: !todo.completed });
};
const addTodo = async (text) => {
const optimisticId = `new_${Date.now()}`;
// Optimistic add
setTodos(prev => [
...prev,
{ id: optimisticId, text, completed: false, _isOptimistic: true }
]);
try {
// Real API call
const response = await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, completed: false })
});
const newTodo = await response.json();
// Replace optimistic with real
setTodos(prev =>
prev.map(todo =>
todo.id === optimisticId ? newTodo : todo
)
);
} catch (error) {
// Rollback
setTodos(prev =>
prev.filter(todo => todo.id !== optimisticId)
);
alert("Failed to add todo");
}
};
return (
<div>
{hasErrors && (
<div className="error-banner">
Some updates failed.
<button onClick={retryAll}>Retry All</button>
</div>
)}
<ul>
{optimisticTodos.map(todo => (
<li
key={todo.id}
className={`todo-item ${todo._isOptimistic ? "optimistic" : ""}`}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
disabled={todo._isOptimistic}
/>
<span style={{
textDecoration: todo.completed ? "line-through" : "none",
opacity: todo._isOptimistic ? 0.6 : 1
}}>
{todo.text}
</span>
{todo._isOptimistic && (
<span className="optimistic-badge">Saving...</span>
)}
</li>
))}
</ul>
</div>
);
}
// 3. Optimistic delete
function useOptimisticDelete(deleteFn) {
const [deletingIds, setDeletingIds] = useState(new Set());
const deleteItem = useCallback(async (id) => {
// Add to deleting set
setDeletingIds(prev => new Set(prev).add(id));
try {
await deleteFn(id);
// Success - remove from set
setDeletingIds(prev => {
const next = new Set(prev);
next.delete(id);
return next;
});
} catch (error) {
// Error - remove from set and show error
setDeletingIds(prev => {
const next = new Set(prev);
next.delete(id);
return next;
});
throw error;
}
}, [deleteFn]);
return { deleteItem, deletingIds };
}Answer: Optimistic updates show immediate UI changes Rollback on API failure Retry mechanism for failed updates Visual feedback for optimistic state
OPTIMISTIC UPDATES PATTERN: Update UI immediately, then sync with server, rollback on failure. BENEFITS: 1. BETTER UX: Immediate feedback 2. PERCEIVED PERFORMANCE: Feels faster 3. SMOOTH INTERACTIONS: No waiting indicators for every action IMPLEMENTATION PATTERNS: 1. OPTIMISTIC CREATE: - Add temporary item with unique ID - Send create request - Replace with real data on success - Remove on failure 2. OPTIMISTIC UPDATE: - Apply changes locally - Mark item as optimistic - Send update request - Update with server response - Rollback on error 3. OPTIMISTIC DELETE: - Remove from UI immediately - Send delete request - Permanently remove on success - Restore on failure CHALLENGES: 1. RACE CONDITIONS: - Multiple updates to same item - Use optimistic IDs - Sequential updates 2. CONFLICT RESOLUTION: - Server vs client state - Last write wins - Conflict detection 3. ERROR HANDLING: - Rollback strategies - Retry mechanisms - User notifications 4. STATE CONSISTENCY: - Offline support - Sync conflicts - Data integrity PERFORMANCE CONSIDERATIONS: - Minimal UI updates - Efficient rollback - Memory management for failed updates - Batch optimistic changes REAL-WORLD EXAMPLES: 1. SOCIAL MEDIA: Likes, comments 2. TODO APPS: Check/uncheck 3. SHOPPING CARTS: Add/remove items 4. MESSAGING: Send messages BEST PRACTICES: 1. Visual feedback for optimistic state 2. Always implement rollback 3. Provide retry options 4. Handle network failures gracefully 5. Test offline scenarios TOOLS & LIBRARIES: 1. React Query: Optimistic updates 2. SWR: Mutation with optimistic UI 3. Apollo Client: Optimistic responses 4. Redux: Middleware for optimistic updates ASKED AT: Meta, Twitter, Instagram (UX-heavy apps)
22. Infinite Scroll with Virtualization
advancedImplement virtualized infinite scroll: 1. Load data in chunks 2. Virtualize DOM elements 3. Handle scroll position 4. Lazy load images 5. Accessibility support
// 1. Virtualized list component
function VirtualizedList({
items,
itemHeight,
containerHeight,
renderItem,
loading,
onLoadMore
}) {
const containerRef = useRef();
const [scrollTop, setScrollTop] = useState(0);
// Calculate visible items
const totalItems = items.length;
const visibleCount = Math.ceil(containerHeight / itemHeight);
// Find start and end indices
const startIndex = Math.max(
0,
Math.floor(scrollTop / itemHeight) - 5 // Buffer
);
const endIndex = Math.min(
totalItems - 1,
startIndex + visibleCount + 10 // Buffer
);
// Handle scroll
const handleScroll = useCallback((e) => {
const scrollTop = e.target.scrollTop;
setScrollTop(scrollTop);
// Check if we need to load more
const scrollBottom = scrollTop + containerHeight;
const totalHeight = totalItems * itemHeight;
if (scrollBottom >= totalHeight - 500 && !loading) {
onLoadMore();
}
}, [containerHeight, totalItems, itemHeight, loading, onLoadMore]);
// Calculate container style
const containerStyle = {
height: containerHeight,
overflowY: "auto",
position: "relative"
};
// Calculate inner container height
const innerHeight = totalItems * itemHeight;
// Calculate transform for visible items
const transform = `translateY(${startIndex * itemHeight}px)`;
return (
<div
ref={containerRef}
style={containerStyle}
onScroll={handleScroll}
role="list"
aria-label="Virtualized list"
>
<div style={{ height: innerHeight, position: "relative" }}>
<div style={{ transform, position: "absolute", width: "100%" }}>
{items.slice(startIndex, endIndex + 1).map((item, index) => {
const actualIndex = startIndex + index;
return (
<div
key={item.id || actualIndex}
style={{ height: itemHeight }}
role="listitem"
aria-posinset={actualIndex + 1}
aria-setsize={totalItems}
>
{renderItem(item, actualIndex)}
</div>
);
})}
</div>
</div>
{loading && (
<div style={{ textAlign: "center", padding: "20px" }}>
Loading more items...
</div>
)}
</div>
);
}
// 2. Infinite scroll hook
function useInfiniteScroll(fetchFn, initialData = []) {
const [items, setItems] = useState(initialData);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [error, setError] = useState(null);
const loadMore = useCallback(async () => {
if (loading || !hasMore) return;
setLoading(true);
setError(null);
try {
const result = await fetchFn(page);
setItems(prev => [...prev, ...result.data]);
setHasMore(result.hasMore);
setPage(prev => prev + 1);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [page, loading, hasMore, fetchFn]);
const reset = useCallback(() => {
setItems(initialData);
setPage(1);
setHasMore(true);
setError(null);
}, [initialData]);
return {
items,
loading,
error,
hasMore,
loadMore,
reset
};
}
// 3. Lazy image component
function LazyImage({ src, alt, width, height }) {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef();
const placeholderRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.unobserve(entry.target);
}
},
{ rootMargin: "50px" }
);
if (placeholderRef.current) {
observer.observe(placeholderRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div
ref={placeholderRef}
style={{
width,
height,
backgroundColor: "#f0f0f0",
position: "relative"
}}
>
{isInView && (
<img
ref={imgRef}
src={src}
alt={alt}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
opacity: isLoaded ? 1 : 0,
transition: "opacity 0.3s"
}}
onLoad={() => setIsLoaded(true)}
loading="lazy"
/>
)}
{!isLoaded && (
<div style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<div className="spinner" />
</div>
)}
</div>
);
}
// 4. Complete example
function InfiniteProductList() {
const fetchProducts = useCallback(async (page) => {
const response = await fetch(`/api/products?page=${page}&limit=20`);
const data = await response.json();
return {
data: data.products,
hasMore: data.page < data.totalPages
};
}, []);
const {
items: products,
loading,
error,
hasMore,
loadMore
} = useInfiniteScroll(fetchProducts, []);
const renderProduct = useCallback((product, index) => (
<div style={{
display: "flex",
padding: "10px",
borderBottom: "1px solid #eee"
}}>
<LazyImage
src={product.image}
alt={product.name}
width={100}
height={100}
/>
<div style={{ marginLeft: "20px" }}>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
</div>
), []);
return (
<div>
<h2>Products ({products.length})</h2>
{error && (
<div style={{ color: "red", padding: "10px" }}>
Error loading products.
<button onClick={loadMore}>Retry</button>
</div>
)}
<VirtualizedList
items={products}
itemHeight={120}
containerHeight={600}
renderItem={renderProduct}
loading={loading}
onLoadMore={loadMore}
/>
{!hasMore && products.length > 0 && (
<div style={{ textAlign: "center", padding: "20px" }}>
No more products to load
</div>
)}
</div>
);
}
// 5. Window scrolling with intersection observer
function useInView(ref, options = {}) {
const [isInView, setIsInView] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsInView(entry.isIntersecting);
}, options);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, [ref, options]);
return isInView;
}Answer: Virtualization renders only visible items Infinite scroll loads data as user scrolls Lazy images with IntersectionObserver Accessibility support with ARIA attributes
VIRTUALIZED INFINITE SCROLL: Render large lists efficiently by only showing visible items. BENEFITS: 1. PERFORMANCE: Render thousands of items 2. MEMORY EFFICIENCY: Only mount visible DOM nodes 3. SMOOTH SCROLLING: No jank 4. BANDWIDTH EFFICIENCY: Load data as needed IMPLEMENTATION PATTERNS: 1. VIRTUALIZATION: - Calculate visible range - Transform container position - Render buffer items - Recycle DOM nodes 2. INFINITE SCROLL: - Detect scroll position - Load more data - Handle loading states - Prevent duplicate requests 3. LAZY LOADING: - IntersectionObserver API - Placeholder while loading - Progressive loading 4. WINDOWING: - Fixed vs variable heights - Dynamic height calculation - Smooth scrolling PERFORMANCE OPTIMIZATIONS: 1. DEBOUNCE SCROLL EVENTS: - Prevent excessive calculations - Use requestAnimationFrame - Batch updates 2. MEMOIZATION: - Memoize render functions - Cache item heights - Stable keys 3. RECYCLING: - Reuse DOM nodes - Pool components - Minimize mount/unmount ACCESSIBILITY: 1. ARIA roles: list, listitem 2. Keyboard navigation 3. Screen reader announcements 4. Focus management CHALLENGES: 1. VARIABLE HEIGHTS: - Measure item heights - Estimate and adjust - Dynamic recalculation 2. SCROLL POSITION RESTORATION: - Save scroll position - Restore after navigation - Handle dynamic content 3. TOUCH DEVICES: - Touch scrolling - Momentum scrolling - Pull-to-refresh REAL-WORLD LIBRARIES: 1. REACT WINDOW: Virtualized lists 2. REACT VIRTUALIZED: Older but feature-rich 3. TANSTACK VIRTUAL: Modern virtualizer 4. REACT-INFINITE-SCROLLER: Infinite scroll BEST PRACTICES: 1. Always implement loading states 2. Handle network errors 3. Provide alternative navigation (pagination) 4. Test with real data 5. Monitor performance metrics MEASUREMENT METRICS: 1. FPS during scroll 2. Memory usage 3. DOM node count 4. Time to interactive ASKED AT: Meta, Google, Airbnb (data-heavy applications)
23. Form Handling with React Hook Form
intermediateImplement complex form handling: 1. Validation with Yup/zod 2. Dynamic fields 3. Multi-step forms 4. File uploads 5. Performance optimization
import { useForm, Controller, useFieldArray } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
// 1. Schema validation with Yup
const userSchema = yup.object({
name: yup.string().required("Name is required").min(2, "Too short"),
email: yup.string().email("Invalid email").required("Email is required"),
age: yup.number().min(18, "Must be 18+").max(100, "Too old"),
password: yup.string()
.required("Password is required")
.min(8, "Must be at least 8 characters")
.matches(/[a-z]/, "Must contain lowercase")
.matches(/[A-Z]/, "Must contain uppercase")
.matches(/\d/, "Must contain number"),
confirmPassword: yup.string()
.oneOf([yup.ref("password")], "Passwords must match")
.required("Confirm password"),
interests: yup.array().of(
yup.object({
name: yup.string().required(),
level: yup.string().oneOf(["beginner", "intermediate", "advanced"])
})
).min(1, "Select at least one interest"),
subscription: yup.boolean(),
avatar: yup.mixed()
.test("fileSize", "File too large", value =>
!value || (value && value.size <= 5000000)
)
.test("fileType", "Unsupported format", value =>
!value || (value && [".jpg", ".png", ".jpeg"].includes(value.name))
)
});
// 2. Complex form with dynamic fields
function UserForm() {
const {
register,
handleSubmit,
control,
watch,
formState: { errors, isSubmitting, isValid, touchedFields },
reset,
setValue,
trigger
} = useForm({
resolver: yupResolver(userSchema),
mode: "onChange", // Validate on change
defaultValues: {
name: "",
email: "",
age: 18,
interests: [{ name: "", level: "beginner" }],
subscription: true
}
});
// Watch form values
const subscription = watch("subscription");
const password = watch("password");
// Dynamic field array
const { fields, append, remove } = useFieldArray({
control,
name: "interests"
});
// Handle file upload
const handleFileChange = (e) => {
const file = e.target.files[0];
setValue("avatar", file);
trigger("avatar"); // Trigger validation
};
// Submit handler
const onSubmit = async (data) => {
try {
// Create FormData for file upload
const formData = new FormData();
Object.keys(data).forEach(key => {
if (key === "avatar" && data[key]) {
formData.append(key, data[key]);
} else if (key === "interests") {
formData.append(key, JSON.stringify(data[key]));
} else {
formData.append(key, data[key]);
}
});
const response = await fetch("/api/users", {
method: "POST",
body: formData
});
if (response.ok) {
alert("User created successfully!");
reset();
}
} catch (error) {
console.error("Error:", error);
}
};
// Dynamic validation
useEffect(() => {
if (password && password.length < 8) {
setValue("passwordStrength", "weak");
} else if (password && password.length >= 8) {
setValue("passwordStrength", "strong");
}
}, [password, setValue]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Text input */}
<div>
<label>Name</label>
<input
{...register("name")}
placeholder="Enter your name"
aria-invalid={errors.name ? "true" : "false"}
/>
{errors.name && (
<span role="alert">{errors.name.message}</span>
)}
</div>
{/* Email with validation */}
<div>
<label>Email</label>
<input
type="email"
{...register("email")}
placeholder="email@example.com"
/>
{errors.email && (
<span role="alert">{errors.email.message}</span>
)}
</div>
{/* Number input */}
<div>
<label>Age</label>
<input
type="number"
{...register("age", { valueAsNumber: true })}
/>
{errors.age && (
<span role="alert">{errors.age.message}</span>
)}
</div>
{/* Password with strength */}
<div>
<label>Password</label>
<input
type="password"
{...register("password")}
/>
{password && (
<div>
Strength: {password.length < 8 ? "Weak" : "Strong"}
</div>
)}
{errors.password && (
<span role="alert">{errors.password.message}</span>
)}
</div>
{/* Confirm password */}
<div>
<label>Confirm Password</label>
<input
type="password"
{...register("confirmPassword")}
/>
{errors.confirmPassword && (
<span role="alert">{errors.confirmPassword.message}</span>
)}
</div>
{/* Dynamic fields */}
<div>
<label>Interests</label>
{fields.map((field, index) => (
<div key={field.id}>
<input
{...register(`interests.${index}.name`)}
placeholder="Interest name"
/>
<select
{...register(`interests.${index}.level`)}
>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button
type="button"
onClick={() => append({ name: "", level: "beginner" })}
>
Add Interest
</button>
{errors.interests && (
<span role="alert">{errors.interests.message}</span>
)}
</div>
{/* File upload */}
<div>
<label>Avatar</label>
<input
type="file"
accept=".jpg,.jpeg,.png"
onChange={handleFileChange}
/>
{errors.avatar && (
<span role="alert">{errors.avatar.message}</span>
)}
</div>
{/* Checkbox */}
<div>
<label>
<input type="checkbox" {...register("subscription")} />
Subscribe to newsletter
</label>
</div>
{/* Conditional field */}
{subscription && (
<div>
<label>Newsletter frequency</label>
<select {...register("frequency")}>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</div>
)}
<button
type="submit"
disabled={isSubmitting || !isValid}
>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
<button type="button" onClick={() => reset()}>
Reset
</button>
</form>
);
}
// 3. Multi-step form
function MultiStepForm() {
const [step, setStep] = useState(1);
const { handleSubmit, trigger, formState } = useForm();
const nextStep = async () => {
// Validate current step
const isValid = await trigger();
if (isValid) {
setStep(prev => prev + 1);
}
};
const prevStep = () => {
setStep(prev => prev - 1);
};
return (
<form onSubmit={handleSubmit(() => {})}>
{step === 1 && (
<div>
<h2>Step 1: Personal Info</h2>
{/* Step 1 fields */}
</div>
)}
{step === 2 && (
<div>
<h2>Step 2: Address</h2>
{/* Step 2 fields */}
</div>
)}
{step === 3 && (
<div>
<h2>Step 3: Review</h2>
{/* Review fields */}
</div>
)}
<div>
{step > 1 && (
<button type="button" onClick={prevStep}>
Previous
</button>
)}
{step < 3 ? (
<button type="button" onClick={nextStep}>
Next
</button>
) : (
<button type="submit">Submit</button>
)}
</div>
<div>
Step {step} of 3
</div>
</form>
);
}
// 4. Custom form hook
function useCustomForm(defaultValues) {
const methods = useForm({ defaultValues });
const [serverErrors, setServerErrors] = useState({});
const handleServerError = useCallback((error) => {
if (error.response?.data?.errors) {
error.response.data.errors.forEach(err => {
methods.setError(err.field, {
type: "server",
message: err.message
});
});
}
setServerErrors(error.response?.data?.errors || {});
}, [methods]);
return {
...methods,
serverErrors,
handleServerError
};
}Answer: React Hook Form with Yup validation Dynamic fields with useFieldArray Multi-step forms with validation per step File uploads with FormData
FORM HANDLING PATTERNS: Manage complex forms with validation, dynamic fields, and performance. BENEFITS: 1. PERFORMANCE: Minimal re-renders 2. FLEXIBILITY: Dynamic forms 3. VALIDATION: Client and server 4. UX: Real-time feedback KEY PATTERNS: 1. CONTROLLED VS UNCONTROLLED: - React Hook Form: Uncontrolled by default - Better performance for large forms - Use Controller for controlled components 2. VALIDATION: - Schema validation (Yup, Zod) - Real-time validation - Async validation - Cross-field validation 3. DYNAMIC FIELDS: - Add/remove fields - Field arrays - Nested fields - Conditional fields 4. FILE HANDLING: - FormData for file uploads - Client-side validation - Progress tracking - Multiple files PERFORMANCE OPTIMIZATIONS: 1. DEBOUNCE VALIDATION: - Prevent excessive validation - Use onBlur or onChange debounced 2. MEMOIZATION: - Memoize form components - Use React.memo - Stable references 3. OPTIMIZED RENDERING: - Split large forms - Lazy load form sections - Virtualize field lists ACCESSIBILITY: 1. ARIA attributes 2. Error announcements 3. Keyboard navigation 4. Screen reader support ERROR HANDLING: 1. Client-side validation 2. Server-side validation 3. Network errors 4. Form submission errors REAL-WORLD LIBRARIES: 1. REACT HOOK FORM: Performance focused 2. FORMIK: Feature rich 3. FINAL FORM: Predictable 4. REACT FINAL FORM: Formik + Final Form BEST PRACTICES: 1. Use schema validation 2. Handle server errors 3. Provide clear error messages 4. Implement loading states 5. Support form reset TESTING: 1. Unit test validation 2. Integration test flows 3. E2E test submissions 4. Accessibility testing ASKED AT: Meta, Google, Stripe (form-heavy applications)
24. Authentication Patterns in React
advancedImplement JWT authentication: 1. Login/logout flows 2. Token refresh 3. Protected routes 4. Role-based access 5. Social login (OAuth)
// 1. Auth context provider
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Initialize auth state
useEffect(() => {
const initAuth = async () => {
try {
// Check for stored token
const token = localStorage.getItem("token");
if (token) {
// Validate token
const user = await validateToken(token);
setUser(user);
}
} catch (err) {
console.error("Auth init error:", err);
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
} finally {
setLoading(false);
}
};
initAuth();
}, []);
// Login function
const login = async (credentials) => {
setError(null);
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(credentials)
});
if (!response.ok) {
throw new Error("Login failed");
}
const { user, token, refreshToken } = await response.json();
// Store tokens
localStorage.setItem("token", token);
localStorage.setItem("refreshToken", refreshToken);
// Set user
setUser(user);
return { success: true, user };
} catch (err) {
setError(err.message);
return { success: false, error: err.message };
}
};
// Logout function
const logout = () => {
// Clear storage
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
// Clear user
setUser(null);
// Optional: Call logout endpoint
fetch("/api/auth/logout", { method: "POST" });
};
// Refresh token
const refreshAuthToken = async () => {
try {
const refreshToken = localStorage.getItem("refreshToken");
if (!refreshToken) {
throw new Error("No refresh token");
}
const response = await fetch("/api/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken })
});
if (!response.ok) {
throw new Error("Token refresh failed");
}
const { token, refreshToken: newRefreshToken } = await response.json();
// Update tokens
localStorage.setItem("token", token);
localStorage.setItem("refreshToken", newRefreshToken);
return token;
} catch (err) {
logout();
throw err;
}
};
// Update user
const updateUser = (updates) => {
setUser(prev => ({ ...prev, ...updates }));
};
const value = {
user,
loading,
error,
login,
logout,
refreshAuthToken,
updateUser,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// 2. Protected route component
function ProtectedRoute({ children, roles = [] }) {
const { user, loading, isAuthenticated } = useAuth();
const location = useLocation();
if (loading) {
return <div>Loading auth...</div>;
}
if (!isAuthenticated) {
// Redirect to login
return <Navigate to="/login" state={{ from: location }} replace />;
}
// Check roles if specified
if (roles.length > 0 && user && !roles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// 3. Auth hook
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within AuthProvider");
}
return context;
}
// 4. Axios interceptor for token refresh
function setupAxiosInterceptors(refreshAuthToken, logout) {
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
axios.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// If 401 and not a retry
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// Queue the request
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then(token => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return axios(originalRequest);
})
.catch(err => Promise.reject(err));
}
originalRequest._retry = true;
isRefreshing = true;
try {
const newToken = await refreshAuthToken();
// Update Authorization header
originalRequest.headers.Authorization = `Bearer ${newToken}`;
// Process queued requests
processQueue(null, newToken);
// Retry original request
return axios(originalRequest);
} catch (refreshError) {
processQueue(refreshError, null);
logout();
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
return Promise.reject(error);
}
);
}
// 5. Social login component
function SocialLogin({ provider }) {
const { login } = useAuth();
const handleSocialLogin = async () => {
// Open OAuth window
const width = 600;
const height = 600;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2;
const url = `/api/auth/${provider}`;
const popup = window.open(
url,
`${provider} Login`,
`width=${width},height=${height},left=${left},top=${top}`
);
// Listen for message from popup
const messageHandler = (event) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === "OAUTH_SUCCESS") {
const { token, user } = event.data;
// Store token
localStorage.setItem("token", token);
// Update auth context
login({ token, user });
// Cleanup
window.removeEventListener("message", messageHandler);
popup.close();
}
};
window.addEventListener("message", messageHandler);
};
return (
<button onClick={handleSocialLogin}>
Continue with {provider}
</button>
);
}
// 6. Role-based component
function RoleGuard({ children, roles }) {
const { user } = useAuth();
if (!user || !roles.includes(user.role)) {
return null;
}
return children;
}
// 7. App setup with auth
function App() {
return (
<AuthProvider>
<Router>
<Routes>
{/* Public routes */}
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
{/* Protected routes */}
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/admin" element={
<ProtectedRoute roles={["admin"]}>
<AdminPanel />
</ProtectedRoute>
} />
</Routes>
</Router>
</AuthProvider>
);
}Answer: JWT authentication with token refresh Protected routes and role-based access Social login with OAuth Axios interceptors for automatic token refresh
AUTHENTICATION PATTERNS: Secure user authentication and authorization in React apps. AUTH METHODS: 1. JWT (JSON WEB TOKENS): - Stateless authentication - Access + refresh tokens - Token expiration - Blacklisting 2. SESSIONS: - Server-side sessions - Cookie-based - CSRF protection - Session storage 3. OAUTH/SOCIAL LOGIN: - Google, Facebook, GitHub - OAuth 2.0 flow - Popup/redirect - Token exchange SECURITY PATTERNS: 1. TOKEN STORAGE: - HttpOnly cookies (most secure) - localStorage (SPA-friendly) - sessionStorage (tab-scoped) - In-memory (most secure, but no persistence) 2. TOKEN REFRESH: - Silent refresh - Refresh token rotation - Concurrent request handling - Failed queue management 3. CSRF PROTECTION: - SameSite cookies - CSRF tokens - Double submit cookies IMPLEMENTATION PATTERNS: 1. AUTH PROVIDER: - Context API - Global auth state - Auth methods 2. PROTECTED ROUTES: - Route guards - Conditional rendering - Redirects 3. ROLE-BASED ACCESS: - User roles/permissions - Component-level guards - Route-level guards 4. SOCIAL LOGIN: - Popup windows - PostMessage communication - Token validation PERFORMANCE OPTIMIZATIONS: - Lazy load auth providers - Cache user data - Minimize token validation requests - Optimize auth state updates ERROR HANDLING: 1. Token expiration 2. Network failures 3. Invalid tokens 4. Server errors BEST PRACTICES: 1. Use HTTPS always 2. Implement proper CORS 3. Validate tokens server-side 4. Log security events 5. Rate limiting REAL-WORLD LIBRARIES: 1. NEXT-AUTH: Next.js authentication 2. AUTH0: Enterprise auth 3. FIREBASE AUTH: Google auth service 4. PASSPORT.JS: Node.js middleware TESTING: 1. Unit test auth logic 2. Integration test flows 3. E2E test login/logout 4. Security testing ASKED AT: Meta, Google, Stripe (security-focused roles)
25. Internationalization (i18n) Patterns
intermediateImplement i18n in React: 1. Translation files 2. Language switching 3. Pluralization 4. Date/number formatting 5. RTL (Right-to-Left) support
// 1. Translation files
const translations = {
en: {
welcome: "Welcome, {{name}}!",
products: {
one: "{{count}} product",
other: "{{count}} products"
},
date: "Today is {{date, datetime}}",
price: "Price: {{price, currency}}",
buttons: {
save: "Save",
cancel: "Cancel"
}
},
fr: {
welcome: "Bienvenue, {{name}} !",
products: {
one: "{{count}} produit",
other: "{{count}} produits"
},
date: "Aujourd'hui, c'est le {{date, datetime}}",
price: "Prix : {{price, currency}}",
buttons: {
save: "Enregistrer",
cancel: "Annuler"
}
},
ar: {
welcome: "مرحبًا {{name}}!",
products: {
one: "{{count}} منتج",
other: "{{count}} منتجات"
},
date: "اليوم هو {{date, datetime}}",
price: "السعر: {{price, currency}}",
buttons: {
save: "حفظ",
cancel: "إلغاء"
}
}
};
// 2. i18n context
const I18nContext = createContext();
function I18nProvider({ children }) {
const [locale, setLocale] = useState(
() => localStorage.getItem("locale") || "en"
);
const [direction, setDirection] = useState("ltr");
// Update direction based on locale
useEffect(() => {
const newDirection = ["ar", "he"].includes(locale) ? "rtl" : "ltr";
setDirection(newDirection);
// Update HTML dir attribute
document.documentElement.dir = newDirection;
document.documentElement.lang = locale;
}, [locale]);
// Format message
const formatMessage = (id, values = {}) => {
const keys = id.split(".");
let message = keys.reduce((obj, key) => obj?.[key], translations[locale]);
if (!message) {
console.warn(`Translation missing: ${id} for locale ${locale}`);
return id;
}
// Handle pluralization
if (typeof message === "object" && "one" in message && "other" in message) {
const count = values.count || 0;
message = count === 1 ? message.one : message.other;
}
// Replace variables
return message.replace(/\{\{(.+?)\}\}/g, (match, key) => {
const [varName, format] = key.trim().split(",");
const value = values[varName.trim()];
if (format) {
return formatValue(value, format.trim());
}
return value != null ? value : match;
});
};
// Format values based on type
const formatValue = (value, format) => {
switch (format) {
case "datetime":
return new Intl.DateTimeFormat(locale, {
dateStyle: "long",
timeStyle: "short"
}).format(new Date(value));
case "currency":
return new Intl.NumberFormat(locale, {
style: "currency",
currency: locale === "en" ? "USD" : "EUR"
}).format(value);
case "number":
return new Intl.NumberFormat(locale).format(value);
case "percent":
return new Intl.NumberFormat(locale, {
style: "percent"
}).format(value);
default:
return value;
}
};
// Change locale
const changeLocale = (newLocale) => {
setLocale(newLocale);
localStorage.setItem("locale", newLocale);
};
const value = {
locale,
direction,
formatMessage,
changeLocale,
formatValue
};
return (
<I18nContext.Provider value={value}>
<div style={{ direction }}>
{children}
</div>
</I18nContext.Provider>
);
}
// 3. Custom hook
function useTranslation() {
const context = useContext(I18nContext);
if (!context) {
throw new Error("useTranslation must be used within I18nProvider");
}
return context;
}
// 4. Translation component
function Trans({ id, values, children }) {
const { formatMessage } = useTranslation();
if (children) {
// Extract text from children for translation
return React.Children.map(children, child => {
if (typeof child === "string") {
return formatMessage(id || child, values);
}
return child;
});
}
return formatMessage(id, values);
}
// 5. Usage examples
function WelcomeMessage({ name }) {
const { formatMessage, locale } = useTranslation();
return (
<div>
<h1>{formatMessage("welcome", { name })}</h1>
<p>{formatMessage("date", { date: new Date() })}</p>
<p>{formatMessage("price", { price: 19.99 })}</p>
{/* Pluralization */}
<p>{formatMessage("products", { count: 1 })}</p>
<p>{formatMessage("products", { count: 5 })}</p>
{/* Using Trans component */}
<Trans id="buttons.save" />
<Trans id="buttons.cancel" />
{/* Language switcher */}
<select
value={locale}
onChange={(e) => changeLocale(e.target.value)}
>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="ar">العربية</option>
</select>
</div>
);
}
// 6. RTL support component
function RTLWrapper({ children }) {
const { direction } = useTranslation();
return (
<div
style={{
direction,
textAlign: direction === "rtl" ? "right" : "left"
}}
>
{children}
</div>
);
}
// 7. Date formatting hook
function useFormattedDate(date, options = {}) {
const { locale } = useTranslation();
return useMemo(() => {
return new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "long",
day: "numeric",
...options
}).format(new Date(date));
}, [date, locale, options]);
}
// 8. Number formatting hook
function useFormattedNumber(number, options = {}) {
const { locale } = useTranslation();
return useMemo(() => {
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
...options
}).format(number);
}, [number, locale, options]);
}
// 9. Async translation loading
function useAsyncTranslations(locale) {
const [translations, setTranslations] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadTranslations = async () => {
setLoading(true);
try {
const response = await fetch(`/locales/${locale}.json`);
const data = await response.json();
setTranslations(data);
} catch (error) {
console.error("Failed to load translations:", error);
} finally {
setLoading(false);
}
};
loadTranslations();
}, [locale]);
return { translations, loading };
}
// 10. App setup
function App() {
return (
<I18nProvider>
<RTLWrapper>
<WelcomeMessage name="John" />
{/* Rest of app */}
</RTLWrapper>
</I18nProvider>
);
}Answer: Translation files with pluralization support Language switching with locale persistence Date/number formatting with Intl API RTL support for Arabic/Hebrew
INTERNATIONALIZATION (I18N) PATTERNS: Adapt applications for different languages and regions. KEY CONCEPTS: 1. LOCALIZATION (L10N): - Translations - Date/time formats - Number/currency formats - Measurement units 2. PLURALIZATION: - Language-specific plural rules - One, few, many, other - ICU MessageFormat 3. RTL SUPPORT: - Right-to-left languages - Text alignment - Layout flipping - Bi-directional text IMPLEMENTATION PATTERNS: 1. TRANSLATION FILES: - JSON structure - Nested keys - Variables/interpolation - Plural forms 2. LANGUAGE SWITCHING: - Context/Redux - URL-based locale - Browser detection - User preference 3. FORMATTING: - Intl API (DateTimeFormat, NumberFormat) - Locale-aware formatting - Timezone support - Calendar systems PERFORMANCE OPTIMIZATIONS: 1. LAZY LOADING: - Load translations on demand - Split by locale - Code splitting 2. CACHING: - Cache translations - Service worker - CDN hosting 3. BUNDLE OPTIMIZATION: - Tree shaking - Remove unused translations - Compression ACCESSIBILITY: 1. Language attribute 2. Direction attribute 3. Screen reader support 4. Keyboard navigation BEST PRACTICES: 1. Use ICU MessageFormat 2. Support RTL early 3. Test with long translations 4. Handle missing translations 5. Provide translation keys REAL-WORLD LIBRARIES: 1. REACT-I18NEXT: Popular i18n framework 2. FORMATJS: ICU MessageFormat 3. LINGUI.JS: Modern i18n 4. I18NEXT: Framework agnostic TESTING: 1. Test all locales 2. Test RTL layout 3. Test date/number formats 4. Test pluralization DEPLOYMENT: 1. Translation management systems 2. Continuous localization 3. Over-the-air updates 4. A/B testing translations ASKED AT: Meta, Google, Microsoft (global products)
26. Micro-frontends with Module Federation
advancedImplement micro-frontends using Webpack Module Federation: 1. Host and remote applications 2. Shared dependencies 3. Dynamic component loading 4. Cross-app communication 5. Deployment strategies
// 1. Host app webpack.config.js
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: "host",
remotes: {
nav: "nav@http://localhost:3001/remoteEntry.js",
dashboard: "dashboard@http://localhost:3002/remoteEntry.js",
auth: "auth@http://localhost:3003/remoteEntry.js"
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
"react-router-dom": { singleton: true, requiredVersion: "^6.0.0" },
"@reduxjs/toolkit": { singleton: true },
// Shared utilities
"shared-utils": { singleton: true }
}
})
]
};
// 2. Remote app (navbar) webpack.config.js
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: "nav",
filename: "remoteEntry.js",
exposes: {
"./Navbar": "./src/components/Navbar",
"./Footer": "./src/components/Footer",
"./Header": "./src/components/Header"
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" }
}
})
]
};
// 3. Host app loading remote components
function App() {
const [Navbar, setNavbar] = useState(null);
const [Dashboard, setDashboard] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// Dynamic import of remote modules
const loadRemotes = async () => {
try {
// Load navbar
const navModule = await import("nav/Navbar");
setNavbar(() => navModule.default);
// Load dashboard (lazy)
const dashboardModule = await import("dashboard/Dashboard");
setDashboard(() => dashboardModule.default);
} catch (err) {
setError("Failed to load remote modules");
console.error(err);
}
};
loadRemotes();
}, []);
if (error) {
return <div>Error: {error}</div>;
}
return (
<div>
{/* Loaded remote components */}
{Navbar && <Navbar />}
<main>
{Dashboard ? (
<Suspense fallback={<div>Loading dashboard...</div>}>
<Dashboard />
</Suspense>
) : (
<div>Loading dashboard...</div>
)}
</main>
{/* Local component */}
<LocalComponent />
</div>
);
}
// 4. Cross-app communication with custom events
const EventBus = {
events: {},
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
},
off(event, callback) {
if (!this.events[event]) return;
const index = this.events[event].indexOf(callback);
if (index > -1) {
this.events[event].splice(index, 1);
}
},
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
try {
callback(data);
} catch (err) {
console.error(`Error in event handler for ${event}:`, err);
}
});
}
};
// Usage in micro-frontends
function Navbar() {
const handleLogout = () => {
EventBus.emit("user:logout", { userId: 123 });
};
return (
<nav>
<button onClick={handleLogout}>Logout</button>
</nav>
);
}
function Dashboard() {
useEffect(() => {
const handleLogout = (data) => {
console.log("Logout event received:", data);
// Clear dashboard data
};
EventBus.on("user:logout", handleLogout);
return () => {
EventBus.off("user:logout", handleLogout);
};
}, []);
return <div>Dashboard content</div>;
}
// 5. Shared state management
const SharedStateContext = createContext();
function SharedStateProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
// Sync state across micro-frontends
useEffect(() => {
const handleThemeChange = (data) => {
setTheme(data.theme);
};
EventBus.on("theme:change", handleThemeChange);
return () => {
EventBus.off("theme:change", handleThemeChange);
};
}, []);
const value = {
user,
theme,
setUser,
setTheme: (newTheme) => {
setTheme(newTheme);
EventBus.emit("theme:change", { theme: newTheme });
}
};
return (
<SharedStateContext.Provider value={value}>
{children}
</SharedStateContext.Provider>
);
}
// 6. Dynamic remote loading with error boundary
function RemoteComponent({ remote, module, fallback, ...props }) {
const [Component, setComponent] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadComponent = async () => {
try {
setLoading(true);
setError(null);
// Import remote module
const module = await import(/* webpackIgnore: true */ remote);
setComponent(() => module.default);
} catch (err) {
setError(err.message);
console.error(`Failed to load ${remote}:`, err);
} finally {
setLoading(false);
}
};
loadComponent();
}, [remote]);
if (loading) return fallback || <div>Loading...</div>;
if (error) return <div>Error loading component: {error}</div>;
if (!Component) return null;
return <Component {...props} />;
}
// Usage
<RemoteComponent
remote="http://localhost:3001/remoteEntry.js"
module="./Navbar"
fallback={<div>Loading navbar...</div>}
/>
// 7. Version management
const RemoteVersionManager = {
versions: {
nav: "1.2.0",
dashboard: "2.1.0",
auth: "1.0.0"
},
getRemoteUrl(moduleName) {
const version = this.versions[moduleName];
return `https://cdn.example.com/${moduleName}/${version}/remoteEntry.js`;
},
updateVersion(moduleName, newVersion) {
this.versions[moduleName] = newVersion;
// Emit event to reload component
EventBus.emit("module:update", { module: moduleName, version: newVersion });
}
};
// 8. Deployment strategy
/*
Production deployment:
1. Each micro-frontend deployed independently
2. CDN for static assets
3. Versioned remoteEntry.js files
4. Blue-green deployment
5. Canary releases
Example CDN URLs:
- https://cdn.example.com/nav/1.2.0/remoteEntry.js
- https://cdn.example.com/dashboard/2.1.0/remoteEntry.js
- https://cdn.example.com/auth/1.0.0/remoteEntry.js
*/Answer: Module Federation for micro-frontends Shared dependencies and singleton React Cross-app communication with event bus Dynamic remote loading with error handling
MICRO-FRONTENDS PATTERN: Split large applications into smaller, independently deployable frontend applications. BENEFITS: 1. INDEPENDENT DEPLOYMENT: Teams deploy independently 2. TECHNOLOGY AGNOSTIC: Different frameworks 3. SCALABILITY: Scale teams and codebase 4. FASTER DEVELOPMENT: Parallel development ARCHITECTURE PATTERNS: 1. BUILD-TIME INTEGRATION: - NPM packages - Monorepo - Shared components 2. RUN-TIME INTEGRATION: - Webpack Module Federation - iframe integration - Web Components 3. SERVER-SIDE INTEGRATION: - Edge Side Includes - Server-side composition - API Gateway KEY CONCEPTS: 1. HOST APPLICATION: - Shell/container app - Loads remote modules - Provides shared dependencies 2. REMOTE APPLICATION: - Exposes components - Independent deployment - Versioned releases 3. SHARED DEPENDENCIES: - Singleton React - Shared state management - Common utilities CHALLENGES: 1. STATE MANAGEMENT: - Cross-app state sharing - Event-based communication - Shared context 2. STYLING: - CSS isolation - Design system consistency - Theme propagation 3. ROUTING: - Cross-app navigation - Nested routes - Route synchronization 4. PERFORMANCE: - Bundle size optimization - Lazy loading - Caching strategies DEPLOYMENT STRATEGIES: 1. INDEPENDENT: Each app has own pipeline 2. COORDINATED: Synchronized releases 3. CANARY: Gradual rollout 4. BLUE-GREEN: Zero downtime TOOLING: 1. WEBPACK MODULE FEDERATION: Runtime integration 2. SINGLE-SPA: Framework-agnostic 3. BIT: Component-driven 4. NX: Monorepo tooling BEST PRACTICES: 1. Define clear contracts 2. Version APIs 3. Implement error boundaries 4. Monitor performance 5. Test integration points REAL-WORLD USERS: 1. SPOTIFY: Web player 2. IKEA: E-commerce 3. DAILYMOTION: Video platform 4. AMAZON: Retail website ASKED AT: Amazon, Spotify, Microsoft (large-scale applications)
27. Design System Component Library
advancedBuild a design system component library: 1. Theme provider with tokens 2. Consistent component API 3. Storybook documentation 4. Accessibility compliance 5. Performance optimization
// 1. Theme tokens
const themeTokens = {
colors: {
primary: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a"
},
neutral: {
50: "#f9fafb",
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
400: "#9ca3af",
500: "#6b7280",
600: "#4b5563",
700: "#374151",
800: "#1f2937",
900: "#111827"
}
},
typography: {
fontFamily: {
sans: ["Inter", "sans-serif"],
mono: ["Roboto Mono", "monospace"]
},
fontSize: {
xs: "0.75rem", // 12px
sm: "0.875rem", // 14px
base: "1rem", // 16px
lg: "1.125rem", // 18px
xl: "1.25rem", // 20px
"2xl": "1.5rem", // 24px
"3xl": "1.875rem", // 30px
"4xl": "2.25rem" // 36px
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700
}
},
spacing: {
0: "0",
1: "0.25rem", // 4px
2: "0.5rem", // 8px
3: "0.75rem", // 12px
4: "1rem", // 16px
6: "1.5rem", // 24px
8: "2rem", // 32px
12: "3rem", // 48px
16: "4rem" // 64px
},
borderRadius: {
none: "0",
sm: "0.125rem", // 2px
base: "0.25rem", // 4px
md: "0.375rem", // 6px
lg: "0.5rem", // 8px
xl: "0.75rem", // 12px
"2xl": "1rem", // 16px
full: "9999px"
}
};
// 2. Theme context
const ThemeContext = createContext();
function DesignSystemProvider({ children, theme = themeTokens }) {
const [mode, setMode] = useState("light");
const currentTheme = useMemo(() => ({
...theme,
mode,
colors: {
...theme.colors,
background: mode === "light" ? "#ffffff" : "#111827",
text: mode === "light" ? "#111827" : "#f9fafb"
}
}), [theme, mode]);
const toggleMode = useCallback(() => {
setMode(prev => prev === "light" ? "dark" : "light");
}, []);
return (
<ThemeContext.Provider value={{ theme: currentTheme, toggleMode, mode }}>
<GlobalStyles theme={currentTheme} />
{children}
</ThemeContext.Provider>
);
}
// Global styles
function GlobalStyles({ theme }) {
return (
<style>
{`
:root {
--color-primary-500: ${theme.colors.primary[500]};
--color-neutral-500: ${theme.colors.neutral[500]};
--font-family-sans: ${theme.typography.fontFamily.sans.join(", ")};
--spacing-4: ${theme.spacing[4]};
--radius-base: ${theme.borderRadius.base};
}
body {
font-family: var(--font-family-sans);
background-color: ${theme.colors.background};
color: ${theme.colors.text};
transition: background-color 0.3s, color 0.3s;
}
`}
</style>
);
}
// 3. Button component with variants
const Button = React.forwardRef(function Button({
children,
variant = "primary",
size = "medium",
isLoading = false,
disabled = false,
leftIcon,
rightIcon,
...props
}, ref) {
const { theme } = useTheme();
const baseStyles = {
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
fontWeight: theme.typography.fontWeight.medium,
border: "1px solid transparent",
borderRadius: theme.borderRadius.base,
cursor: disabled ? "not-allowed" : "pointer",
transition: "all 0.2s",
outline: "none",
"&:focus-visible": {
boxShadow: `0 0 0 3px ${theme.colors.primary[100]}`
}
};
const variants = {
primary: {
backgroundColor: theme.colors.primary[600],
color: "white",
"&:hover:not(:disabled)": {
backgroundColor: theme.colors.primary[700]
},
"&:active:not(:disabled)": {
backgroundColor: theme.colors.primary[800]
}
},
secondary: {
backgroundColor: theme.colors.neutral[200],
color: theme.colors.neutral[800],
"&:hover:not(:disabled)": {
backgroundColor: theme.colors.neutral[300]
}
},
ghost: {
backgroundColor: "transparent",
color: theme.colors.neutral[700],
"&:hover:not(:disabled)": {
backgroundColor: theme.colors.neutral[100]
}
}
};
const sizes = {
small: {
padding: `${theme.spacing[1]} ${theme.spacing[2]}`,
fontSize: theme.typography.fontSize.sm
},
medium: {
padding: `${theme.spacing[2]} ${theme.spacing[4]}`,
fontSize: theme.typography.fontSize.base
},
large: {
padding: `${theme.spacing[3]} ${theme.spacing[6]}`,
fontSize: theme.typography.fontSize.lg
}
};
const styles = {
...baseStyles,
...variants[variant],
...sizes[size],
opacity: disabled ? 0.6 : 1
};
return (
<button
ref={ref}
style={styles}
disabled={disabled || isLoading}
aria-busy={isLoading}
{...props}
>
{isLoading && (
<Spinner size="sm" color="currentColor" />
)}
{!isLoading && leftIcon && (
<span style={{ marginRight: theme.spacing[1] }}>
{leftIcon}
</span>
)}
{children}
{!isLoading && rightIcon && (
<span style={{ marginLeft: theme.spacing[1] }}>
{rightIcon}
</span>
)}
</button>
);
});
// 4. Input component
const Input = React.forwardRef(function Input({
label,
error,
helperText,
required,
disabled,
leftElement,
rightElement,
...props
}, ref) {
const { theme } = useTheme();
const id = useId();
return (
<div style={{ marginBottom: theme.spacing[4] }}>
{label && (
<label
htmlFor={id}
style={{
display: "block",
marginBottom: theme.spacing[1],
fontWeight: theme.typography.fontWeight.medium,
color: error ? theme.colors.danger : theme.colors.neutral[700]
}}
>
{label}
{required && (
<span style={{ color: theme.colors.danger }}>*</span>
)}
</label>
)}
<div style={{ position: "relative" }}>
{leftElement && (
<div style={{
position: "absolute",
left: theme.spacing[2],
top: "50%",
transform: "translateY(-50%)"
}}>
{leftElement}
</div>
)}
<input
ref={ref}
id={id}
style={{
width: "100%",
padding: `${theme.spacing[2]} ${theme.spacing[3]}`,
paddingLeft: leftElement ? `calc(${theme.spacing[3]} + 24px)` : theme.spacing[3],
paddingRight: rightElement ? `calc(${theme.spacing[3]} + 24px)` : theme.spacing[3],
border: `1px solid ${error ? theme.colors.danger : theme.colors.neutral[300]}`,
borderRadius: theme.borderRadius.base,
fontSize: theme.typography.fontSize.base,
color: theme.colors.neutral[900],
backgroundColor: disabled ? theme.colors.neutral[100] : "white",
transition: "border-color 0.2s",
"&:focus": {
outline: "none",
borderColor: error ? theme.colors.danger : theme.colors.primary[500],
boxShadow: `0 0 0 3px ${error ? theme.colors.dangerLight : theme.colors.primary[100]}`
},
"&:disabled": {
cursor: "not-allowed",
opacity: 0.6
}
}}
disabled={disabled}
aria-invalid={error ? "true" : "false"}
aria-describedby={error ? `${id}-error` : helperText ? `${id}-helper` : undefined}
{...props}
/>
{rightElement && (
<div style={{
position: "absolute",
right: theme.spacing[2],
top: "50%",
transform: "translateY(-50%)"
}}>
{rightElement}
</div>
)}
</div>
{error && (
<div
id={`${id}-error`}
style={{
marginTop: theme.spacing[1],
color: theme.colors.danger,
fontSize: theme.typography.fontSize.sm
}}
role="alert"
>
{error}
</div>
)}
{helperText && !error && (
<div
id={`${id}-helper`}
style={{
marginTop: theme.spacing[1],
color: theme.colors.neutral[500],
fontSize: theme.typography.fontSize.sm
}}
>
{helperText}
</div>
)}
</div>
);
});
// 5. Compound components (Accordion)
const AccordionContext = createContext();
function Accordion({ children, allowMultiple = false }) {
const [openItems, setOpenItems] = useState([]);
const toggleItem = useCallback((itemId) => {
setOpenItems(prev => {
if (allowMultiple) {
return prev.includes(itemId)
? prev.filter(id => id !== itemId)
: [...prev, itemId];
} else {
return prev.includes(itemId) ? [] : [itemId];
}
});
}, [allowMultiple]);
return (
<AccordionContext.Provider value={{ openItems, toggleItem }}>
<div role="region">{children}</div>
</AccordionContext.Provider>
);
}
function AccordionItem({ children, id }) {
const { openItems, toggleItem } = useContext(AccordionContext);
const isOpen = openItems.includes(id);
return (
<div style={{ borderBottom: "1px solid #e5e7eb" }}>
{React.Children.map(children, child =>
React.cloneElement(child, { isOpen, toggle: () => toggleItem(id) })
)}
</div>
);
}
function AccordionTrigger({ children, isOpen, toggle }) {
return (
<button
onClick={toggle}
aria-expanded={isOpen}
style={{
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "1rem",
backgroundColor: "transparent",
border: "none",
cursor: "pointer"
}}
>
{children}
<span>{isOpen ? "▼" : "▶"}</span>
</button>
);
}
function AccordionContent({ children, isOpen }) {
return (
<div
style={{
padding: isOpen ? "1rem" : "0 1rem",
maxHeight: isOpen ? "1000px" : "0",
overflow: "hidden",
transition: "all 0.3s"
}}
aria-hidden={!isOpen}
>
{children}
</div>
);
}
// Usage
<Accordion allowMultiple>
<AccordionItem id="1">
<AccordionTrigger>Section 1</AccordionTrigger>
<AccordionContent>Content 1</AccordionContent>
</AccordionItem>
<AccordionItem id="2">
<AccordionTrigger>Section 2</AccordionTrigger>
<AccordionContent>Content 2</AccordionContent>
</AccordionItem>
</Accordion>
// 6. Storybook stories
/*
// Button.stories.js
export default {
title: "Components/Button",
component: Button,
argTypes: {
variant: {
control: { type: "select" },
options: ["primary", "secondary", "ghost"]
},
size: {
control: { type: "select" },
options: ["small", "medium", "large"]
}
}
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: "Primary Button",
variant: "primary"
};
export const Secondary = Template.bind({});
Secondary.args = {
children: "Secondary Button",
variant: "secondary"
};
*/
// 7. Performance optimization
const MemoizedButton = React.memo(Button);
const MemoizedInput = React.memo(Input);
// 8. Theme hook
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within DesignSystemProvider");
}
return context;
}
// 9. Design tokens access
function useDesignToken(tokenPath) {
const { theme } = useTheme();
return tokenPath.split(".").reduce((obj, key) => obj?.[key], theme);
}
// Usage: const primaryColor = useDesignToken("colors.primary.500");Answer: Design tokens for consistent styling Theme provider with light/dark mode Compound components for complex UIs Storybook for component documentation
DESIGN SYSTEM PATTERNS: Create consistent, reusable component libraries for large applications. KEY ELEMENTS: 1. DESIGN TOKENS: - Colors, typography, spacing - Breakpoints, shadows, borders - Semantic naming - Theme variations 2. COMPONENT LIBRARY: - Base components (Button, Input) - Complex components (Modal, DataTable) - Layout components (Grid, Container) - Compound components 3. DOCUMENTATION: - Storybook for component examples - Usage guidelines - Accessibility documentation - Code examples ARCHITECTURE PATTERNS: 1. THEME PROVIDER: - Context-based theming - Light/dark mode - Custom theme override - CSS-in-JS integration 2. COMPONENT API DESIGN: - Consistent prop naming - Compound components - Render props - Forward refs 3. STYLING APPROACHES: - CSS-in-JS (styled-components, Emotion) - CSS Modules - Utility-first (Tailwind) - BEM methodology PERFORMANCE OPTIMIZATIONS: 1. Component memoization 2. CSS extraction 3. Tree shaking 4. Code splitting 5. Lazy loading components ACCESSIBILITY FEATURES: 1. ARIA attributes 2. Keyboard navigation 3. Focus management 4. Screen reader support 5. Color contrast compliance TESTING STRATEGIES: 1. Visual regression testing 2. Component unit tests 3. Accessibility audits 4. Cross-browser testing 5. Performance testing VERSIONING & RELEASES: 1. Semantic versioning 2. Changelog 3. Breaking change management 4. Deprecation policies INTEGRATION PATTERNS: 1. NPM package 2. Monorepo 3. Module Federation 4. CDN distribution REAL-WORLD DESIGN SYSTEMS: 1. MATERIAL-UI: Google's design system 2. CHAKRA UI: Accessible components 3. ANT DESIGN: Enterprise design 4. CARBON: IBM design system 5. ATLASSIAN DESIGN SYSTEM: Jira, Confluence BEST PRACTICES: 1. Design tokens first 2. Accessibility by default 3. Consistent API 4. Comprehensive documentation 5. Performance monitoring TOOLING: 1. STORYBOOK: Component documentation 2. CHROMATIC: Visual testing 3. FIGMA: Design handoff 4. THEMING TOOLS: Style Dictionary ASKED AT: Meta, Google, Microsoft, Airbnb (design systems teams)
28. Testing Strategies for React Applications
intermediateImplement comprehensive testing: 1. Unit tests with Jest 2. Integration tests 3. E2E tests with Cypress 4. Testing hooks 5. Mocking strategies
// 1. Unit test for component
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Counter from "./Counter";
describe("Counter", () => {
it("renders initial count", () => {
render(<Counter initialCount={5} />);
expect(screen.getByText("Count: 5")).toBeInTheDocument();
});
it("increments count when increment button is clicked", async () => {
render(<Counter initialCount={0} />);
const incrementButton = screen.getByRole("button", { name: /increment/i });
await userEvent.click(incrementButton);
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});
it("decrements count when decrement button is clicked", async () => {
render(<Counter initialCount={10} />);
const decrementButton = screen.getByRole("button", { name: /decrement/i });
await userEvent.click(decrementButton);
expect(screen.getByText("Count: 9")).toBeInTheDocument();
});
it("resets count when reset button is clicked", async () => {
render(<Counter initialCount={20} />);
const resetButton = screen.getByRole("button", { name: /reset/i });
await userEvent.click(resetButton);
expect(screen.getByText("Count: 0")).toBeInTheDocument();
});
it("calls onChange callback when count changes", () => {
const handleChange = jest.fn();
render(<Counter initialCount={0} onChange={handleChange} />);
fireEvent.click(screen.getByText("Increment"));
expect(handleChange).toHaveBeenCalledWith(1);
});
});
// 2. Testing custom hook
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
it("should use initial value", () => {
const { result } = renderHook(() => useCounter(5));
expect(result.current.count).toBe(5);
});
it("should increment", () => {
const { result } = renderHook(() => useCounter(0));
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it("should decrement", () => {
const { result } = renderHook(() => useCounter(10));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(9);
});
it("should reset", () => {
const { result } = renderHook(() => useCounter(20));
act(() => {
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(0);
});
it("should not go below min", () => {
const { result } = renderHook(() => useCounter(0, { min: 0 }));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(0);
});
});
// 3. Integration test for form
import { waitFor } from "@testing-library/react";
import LoginForm from "./LoginForm";
import { login } from "./api";
jest.mock("./api");
describe("LoginForm", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("submits form with valid data", async () => {
const mockLogin = login.mockResolvedValue({ success: true });
const onSuccess = jest.fn();
render(<LoginForm onSuccess={onSuccess} />);
const emailInput = screen.getByLabelText(/email/i);
const passwordInput = screen.getByLabelText(/password/i);
const submitButton = screen.getByRole("button", { name: /login/i });
// Fill form
await userEvent.type(emailInput, "test@example.com");
await userEvent.type(passwordInput, "password123");
// Submit
await userEvent.click(submitButton);
// Check API call
expect(mockLogin).toHaveBeenCalledWith({
email: "test@example.com",
password: "password123"
});
// Check success callback
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled();
});
});
it("shows error on failed login", async () => {
login.mockRejectedValue(new Error("Invalid credentials"));
render(<LoginForm />);
const emailInput = screen.getByLabelText(/email/i);
const passwordInput = screen.getByLabelText(/password/i);
const submitButton = screen.getByRole("button", { name: /login/i });
// Fill form
await userEvent.type(emailInput, "wrong@example.com");
await userEvent.type(passwordInput, "wrong");
// Submit
await userEvent.click(submitButton);
// Check error message
await waitFor(() => {
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
});
});
it("validates required fields", async () => {
render(<LoginForm />);
const submitButton = screen.getByRole("button", { name: /login/i });
await userEvent.click(submitButton);
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
expect(screen.getByText(/password is required/i)).toBeInTheDocument();
expect(login).not.toHaveBeenCalled();
});
});
// 4. Mocking strategies
// Mock module
export const mockApi = {
fetchUser: jest.fn(),
updateUser: jest.fn(),
deleteUser: jest.fn()
};
jest.mock("./api", () => mockApi);
// Mock window properties
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
});
});
// Mock router
import { MemoryRouter } from "react-router-dom";
describe("App with router", () => {
it("navigates to about page", async () => {
render(
<MemoryRouter initialEntries={["/about"]}>
<App />
</MemoryRouter>
);
expect(screen.getByText(/about page/i)).toBeInTheDocument();
});
});
// Mock context
const mockAuthContext = {
user: { name: "Test User" },
login: jest.fn(),
logout: jest.fn()
};
jest.mock("./AuthContext", () => ({
useAuth: () => mockAuthContext
}));
// 5. E2E test with Cypress
/*
// login.spec.cy.js
describe("Login", () => {
beforeEach(() => {
cy.visit("/login");
});
it("should login with valid credentials", () => {
cy.intercept("POST", "/api/login", {
statusCode: 200,
body: { user: { name: "John Doe" } }
}).as("loginRequest");
cy.get("[data-testid=email]").type("test@example.com");
cy.get("[data-testid=password]").type("password123");
cy.get("[data-testid=login-button]").click();
cy.wait("@loginRequest");
cy.url().should("include", "/dashboard");
cy.contains("Welcome, John Doe").should("be.visible");
});
it("should show error with invalid credentials", () => {
cy.intercept("POST", "/api/login", {
statusCode: 401,
body: { error: "Invalid credentials" }
}).as("loginRequest");
cy.get("[data-testid=email]").type("wrong@example.com");
cy.get("[data-testid=password]").type("wrong");
cy.get("[data-testid=login-button]").click();
cy.wait("@loginRequest");
cy.contains("Invalid credentials").should("be.visible");
});
});
*/
// 6. Snapshot testing
it("renders correctly", () => {
const { asFragment } = render(<Button>Click me</Button>);
expect(asFragment()).toMatchSnapshot();
});
// 7. Performance testing
import { measurePerformance } from "reassure";
test("Button render performance", async () => {
await measurePerformance(<Button>Click me</Button>);
});
// 8. Accessibility testing
import { axe } from "jest-axe";
it("should have no accessibility violations", async () => {
const { container } = render(<Button>Accessible button</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
// 9. Test coverage configuration
/*
// jest.config.js
module.exports = {
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts",
"!src/index.js",
"!src/reportWebVitals.js"
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
*/
// 10. CI/CD test configuration
/*
// .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "18"
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test -- --coverage
- name: Run integration tests
run: npm run test:integration
- name: Run E2E tests
run: npm run test:e2e
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
*/
// 11. Mock server for testing
import { setupServer } from "msw/node";
import { rest } from "msw";
const server = setupServer(
rest.get("/api/user", (req, res, ctx) => {
return res(
ctx.json({ name: "John Doe" })
);
}),
rest.post("/api/login", (req, res, ctx) => {
return res(
ctx.json({ token: "fake-token" })
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());Answer: Unit tests with Jest and React Testing Library Integration tests for user flows E2E tests with Cypress Mocking APIs and dependencies
TESTING STRATEGIES FOR REACT: Comprehensive testing pyramid for React applications. TESTING PYRAMID: 1. UNIT TESTS (60-70%): - Test individual components - Test custom hooks - Test utility functions - Fast, isolated tests 2. INTEGRATION TESTS (20-30%): - Test component interactions - Test user flows - Test with context/providers - Test API integrations 3. E2E TESTS (10%): - Test complete user journeys - Test across multiple pages - Test in real browser - Slow but realistic TOOLING: 1. UNIT TESTING: - Jest: Test runner - React Testing Library: Component testing - @testing-library/user-event: User interactions 2. INTEGRATION TESTING: - Same as unit + MSW for API mocking - Jest DOM for assertions - React Testing Library for rendering 3. E2E TESTING: - Cypress: Modern E2E - Playwright: Cross-browser - Selenium: Legacy MOCKING STRATEGIES: 1. API MOCKS: - MSW (Mock Service Worker) - Jest mock functions - Fetch/axios interceptors 2. CONTEXT MOCKS: - Mock React context - Mock Redux store - Mock Router 3. WINDOW MOCKS: - localStorage/sessionStorage - matchMedia - IntersectionObserver - ResizeObserver TESTING PATTERNS: 1. COMPONENT TESTS: - Render assertions - User interactions - Props testing - Event handlers 2. HOOK TESTS: - renderHook from RTL - act for state updates - Async hook testing 3. FORM TESTS: - Form submission - Validation errors - Field interactions 4. ASYNC TESTS: - waitFor for async updates - Mock timers - Loading states PERFORMANCE TESTING: 1. REASSURE: Performance regression 2. LIGHTHOUSE CI: Performance scores 3. WEB VITALS: Core web vitals ACCESSIBILITY TESTING: 1. JEST-AXE: Accessibility violations 2. LIGHTHOUSE: Accessibility audits 3. MANUAL: Screen reader testing COVERAGE METRICS: 1. Line coverage 2. Branch coverage 3. Function coverage 4. Statement coverage BEST PRACTICES: 1. Test behavior, not implementation 2. Use data-testid sparingly 3. Prefer userEvent over fireEvent 4. Clean up after tests 5. Run tests in CI/CD TEST ORGANIZATION: 1. Arrange-Act-Assert pattern 2. Descriptive test names 3. Shared test utilities 4. Test fixtures DEBUGGING: 1. screen.debug() 2. Jest --verbose 3. Cypress test runner 4. React DevTools ASKED AT: Meta, Google, Netflix (testing-focused roles)
29. Performance Monitoring and Optimization
advancedImplement performance monitoring: 1. Web Vitals tracking 2. Bundle analysis 3. React Profiler integration 4. Custom performance metrics 5. Real User Monitoring (RUM)
// 1. Web Vitals tracking
import { getCLS, getFID, getLCP, getFCP, getTTFB } from "web-vitals";
function sendToAnalytics(metric) {
const body = {
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType
};
// Send to your analytics
fetch("/api/analytics/web-vitals", {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" }
});
// Log to console in development
if (process.env.NODE_ENV === "development") {
console.log(metric);
}
}
// Initialize web vitals tracking
function initWebVitals() {
if (typeof window === "undefined") return;
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
getFCP(sendToAnalytics);
getTTFB(sendToAnalytics);
}
// 2. React Profiler integration
function ProfilerWrapper({ children, id }) {
const handleRender = useCallback(
(id, phase, actualDuration, baseDuration, startTime, commitTime) => {
// Performance logging
console.log(`Profiler [${id}]:`, {
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
commitTime - startTime
});
// Send to analytics for production
if (process.env.NODE_ENV === "production") {
const metric = {
componentId: id,
phase,
duration: actualDuration,
timestamp: Date.now()
};
// Queue for batch sending
performanceMetricsQueue.push(metric);
}
},
[]
);
return (
<React.Profiler id={id} onRender={handleRender}>
{children}
</React.Profiler>
);
}
// 3. Custom performance metrics
class PerformanceMonitor {
constructor() {
this.metrics = new Map();
this.observers = [];
}
// Start measurement
start(metricName) {
if (this.metrics.has(metricName)) {
console.warn(`Metric "${metricName}" already started`);
return;
}
this.metrics.set(metricName, {
startTime: performance.now(),
marks: []
});
}
// Mark a point in time
mark(metricName, markName) {
const metric = this.metrics.get(metricName);
if (!metric) {
console.warn(`Metric "${metricName}" not found`);
return;
}
metric.marks.push({
name: markName,
time: performance.now() - metric.startTime
});
}
// End measurement
end(metricName) {
const metric = this.metrics.get(metricName);
if (!metric) {
console.warn(`Metric "${metricName}" not found`);
return;
}
const endTime = performance.now();
const duration = endTime - metric.startTime;
// Notify observers
this.observers.forEach(observer => {
observer({
name: metricName,
duration,
marks: metric.marks,
startTime: metric.startTime,
endTime
});
});
// Remove from metrics
this.metrics.delete(metricName);
return duration;
}
// Add observer
observe(callback) {
this.observers.push(callback);
return () => {
const index = this.observers.indexOf(callback);
if (index > -1) this.observers.splice(index, 1);
};
}
}
// Singleton instance
export const perfMonitor = new PerformanceMonitor();
// 4. Hook for measuring component render time
function useRenderTime(componentName) {
const startTime = useRef(performance.now());
useEffect(() => {
const renderTime = performance.now() - startTime.current;
// Log render time
if (process.env.NODE_ENV === "development") {
console.log(`[${componentName}] Render time:`, renderTime.toFixed(2), "ms");
}
// Send to analytics if too slow
if (renderTime > 100) { // 100ms threshold
perfMonitor.end(`${componentName}_render`);
}
});
}
// 5. Bundle analysis plugin
function BundleAnalyzer() {
useEffect(() => {
if (process.env.NODE_ENV === "development") {
import("webpack-bundle-analyzer").then(({ BundleAnalyzerPlugin }) => {
// Could trigger analysis
});
}
}, []);
return null;
}
// 6. Real User Monitoring (RUM) setup
class RUMTracker {
constructor() {
this.sessionId = this.generateSessionId();
this.pageLoadTime = null;
this.resourceTimings = [];
this.init();
}
generateSessionId() {
return "session_" + Math.random().toString(36).substr(2, 9);
}
init() {
// Track page load
window.addEventListener("load", () => {
this.pageLoadTime = performance.now();
this.trackPageLoad();
});
// Track resource timings
if (performance.getEntriesByType) {
const resources = performance.getEntriesByType("resource");
this.resourceTimings = resources.map(resource => ({
name: resource.name,
duration: resource.duration,
initiatorType: resource.initiatorType,
transferSize: resource.transferSize
}));
}
// Track errors
window.addEventListener("error", this.trackError.bind(this));
// Track unhandled rejections
window.addEventListener("unhandledrejection", this.trackPromiseError.bind(this));
}
trackPageLoad() {
const timing = performance.timing;
const metrics = {
sessionId: this.sessionId,
page: window.location.pathname,
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
request: timing.responseStart - timing.requestStart,
response: timing.responseEnd - timing.responseStart,
domInteractive: timing.domInteractive - timing.navigationStart,
domComplete: timing.domComplete - timing.navigationStart,
loadEvent: timing.loadEventEnd - timing.navigationStart,
firstPaint: performance.getEntriesByName("first-paint")[0]?.startTime,
firstContentfulPaint: performance.getEntriesByName("first-contentful-paint")[0]?.startTime
};
this.sendToBackend("page-load", metrics);
}
trackError(event) {
const errorData = {
sessionId: this.sessionId,
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error?.stack
};
this.sendToBackend("error", errorData);
}
trackPromiseError(event) {
const errorData = {
sessionId: this.sessionId,
reason: event.reason,
promise: event.promise
};
this.sendToBackend("promise-error", errorData);
}
sendToBackend(type, data) {
// Use Beacon API for reliability
const blob = new Blob([JSON.stringify({
type,
data,
timestamp: Date.now(),
userAgent: navigator.userAgent,
viewport: `${window.innerWidth}x${window.innerHeight}`
})], { type: "application/json" });
navigator.sendBeacon("/api/rum", blob);
}
}
// Initialize RUM
if (typeof window !== "undefined") {
window.__RUM = new RUMTracker();
}
// 7. Performance optimization hook
function usePerformanceOptimization() {
const [isSlowConnection, setIsSlowConnection] = useState(false);
useEffect(() => {
// Check connection speed
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
const handleChange = () => {
const isSlow = connection.saveData ||
connection.effectiveType === "slow-2g" ||
connection.effectiveType === "2g";
setIsSlowConnection(isSlow);
// Emit event for other components
window.dispatchEvent(new CustomEvent("connection-change", {
detail: { isSlow }
}));
};
connection.addEventListener("change", handleChange);
handleChange(); // Initial check
return () => connection.removeEventListener("change", handleChange);
}
}, []);
return { isSlowConnection };
}
// 8. Lazy loading based on connection
function AdaptiveComponent({ fullComponent, lightComponent, fallback }) {
const { isSlowConnection } = usePerformanceOptimization();
const Component = isSlowConnection ? lightComponent : fullComponent;
const LazyComponent = React.lazy(() =>
isSlowConnection
? import(`./${lightComponent}`)
: import(`./${fullComponent}`)
);
return (
<Suspense fallback={fallback}>
<LazyComponent />
</Suspense>
);
}
// 9. Memory leak detection
function useMemoryLeakDetection(componentName) {
useEffect(() => {
const initialMemory = performance.memory?.usedJSHeapSize;
return () => {
// Check memory on unmount
setTimeout(() => {
const currentMemory = performance.memory?.usedJSHeapSize;
if (currentMemory && initialMemory) {
const diff = currentMemory - initialMemory;
if (diff > 10 * 1024 * 1024) { // 10MB threshold
console.warn(`Potential memory leak in ${componentName}: +${(diff / 1024 / 1024).toFixed(2)}MB`);
}
}
}, 1000);
};
}, [componentName]);
}
// 10. Performance budget monitoring
const performanceBudget = {
bundleSize: 200 * 1024, // 200KB
firstContentfulPaint: 1800, // 1.8s
largestContentfulPaint: 2500, // 2.5s
cumulativeLayoutShift: 0.1,
timeToInteractive: 3800 // 3.8s
};
function checkPerformanceBudget(metrics) {
const violations = [];
if (metrics.fcp > performanceBudget.firstContentfulPaint) {
violations.push(`FCP: ${metrics.fcp}ms > ${performanceBudget.firstContentfulPaint}ms`);
}
if (metrics.lcp > performanceBudget.largestContentfulPaint) {
violations.push(`LCP: ${metrics.lcp}ms > ${performanceBudget.largestContentfulPaint}ms`);
}
if (metrics.cls > performanceBudget.cumulativeLayoutShift) {
violations.push(`CLS: ${metrics.cls} > ${performanceBudget.cumulativeLayoutShift}`);
}
if (violations.length > 0) {
console.warn("Performance budget violations:", violations);
// Send to monitoring service
sendToMonitoringService({
type: "performance-budget-violation",
violations
});
}
}Answer: Web Vitals tracking for Core Web Vitals React Profiler for component performance Real User Monitoring (RUM) for production Performance budgets and optimization
PERFORMANCE MONITORING PATTERNS: Measure and optimize React application performance. KEY METRICS: 1. CORE WEB VITALS: - LCP (Largest Contentful Paint): Loading performance - FID (First Input Delay): Interactivity - CLS (Cumulative Layout Shift): Visual stability 2. REACT-SPECIFIC METRICS: - Component render time - Re-render count - Bundle size - Memory usage MONITORING STRATEGIES: 1. SYNTHETIC MONITORING: - Lab testing - Lighthouse - WebPageTest - Build-time checks 2. REAL USER MONITORING (RUM): - Production performance - User experience - Error tracking - Business metrics TOOLING: 1. GOOGLE TOOLS: - Lighthouse: Auditing - PageSpeed Insights: Recommendations - Search Console: Field data 2. COMMERCIAL TOOLS: - New Relic: Full-stack APM - Datadog: Performance monitoring - Sentry: Error tracking 3. SELF-HOSTED: - Prometheus + Grafana - Elastic APM - OpenTelemetry REACT-SPECIFIC OPTIMIZATIONS: 1. BUNDLE OPTIMIZATION: - Code splitting - Tree shaking - Compression - CDN caching 2. RENDER OPTIMIZATION: - Memoization - Virtualization - Lazy loading - Concurrent features 3. NETWORK OPTIMIZATION: - HTTP/2 - Brotli compression - Image optimization - Service workers PERFORMANCE BUDGETS: 1. Bundle size limits 2. Time-based thresholds 3. Score-based targets 4. User-centric metrics IMPLEMENTATION PATTERNS: 1. MEASUREMENT: - Performance API - React Profiler - Custom timers 2. REPORTING: - Analytics integration - Alerting - Dashboards 3. OPTIMIZATION: - A/B testing - Progressive enhancement - Adaptive loading BEST PRACTICES: 1. Measure before optimizing 2. Set realistic budgets 3. Monitor trends, not absolutes 4. Focus on user experience 5. Test on real devices ERROR MONITORING: 1. JavaScript errors 2. Network failures 3. Resource loading errors 4. Promise rejections BUSINESS METRICS CORRELATION: 1. Conversion rates vs performance 2. User engagement vs speed 3. Revenue impact 4. SEO rankings CONTINUOUS MONITORING: 1. CI/CD integration 2. Automated alerts 3. Performance regression testing 4. Canary analysis ASKED AT: Google, Meta, Netflix (performance engineering)