Master production-ready React development with best practices, performance optimization, error handling, and scalable application architecture.
Writing React code that works is one thing - writing React code that scales, performs well, and is maintainable is another. This lesson covers the essential best practices that separate professional React developers from beginners.
You'll learn the patterns, optimizations, and architectural decisions that power production React applications at companies like Facebook, Netflix, and Airbnb. From performance optimization techniques to error handling strategies, you'll master the skills needed to build enterprise-grade React applications.
These aren't just theoretical concepts - they're battle-tested practices that will make your code more robust, your applications faster, and your development workflow more efficient.
Performance optimization in React requires understanding when and why components re-render, and how to prevent unnecessary work.
Understanding React Rendering:
React's rendering process has two phases:
Common Performance Issues:
React.memo for Component Memoization:
React.memo prevents re-renders when props haven't changed:
1import { memo } from 'react';
2
3// Without memoization - re-renders every time parent renders
4function ExpensiveComponent({ data, onUpdate }) {
5 console.log('ExpensiveComponent rendered');
6
7 return (
8 <div>
9 <h3>{data.title}</h3>
10 <p>{data.description}</p>
11 <button onClick={onUpdate}>Update</button>
12 </div>
13 );
14}
15
16// With memoization - only re-renders when props change
17const MemoizedExpensiveComponent = memo(ExpensiveComponent);
18
19// Custom comparison function for complex props
20const CustomMemoComponent = memo(ExpensiveComponent, (prevProps, nextProps) => {
21 // Return true if props are equal (skip re-render)
22 // Return false if props are different (allow re-render)
23 return (
24 prevProps.data.id === nextProps.data.id &&
25 prevProps.data.title === nextProps.data.title &&
26 prevProps.onUpdate === nextProps.onUpdate
27 );
28});useMemo for Expensive Calculations:
useMemo memoizes the result of expensive computations:
1import { useMemo, useState } from 'react';
2
3function DataProcessor({ items, filter }) {
4 const [searchTerm, setSearchTerm] = useState('');
5
6 // Without memoization - recalculates every render
7 const expensiveProcessing = () => {
8 console.log('Processing data...');
9 return items
10 .filter(item => item.category === filter)
11 .map(item => ({
12 ...item,
13 score: calculateComplexScore(item)
14 }))
15 .sort((a, b) => b.score - a.score);
16 };
17
18 // With memoization - only recalculates when dependencies change
19 const processedData = useMemo(() => {
20 console.log('Processing data with useMemo...');
21 return items
22 .filter(item => item.category === filter)
23 .map(item => ({
24 ...item,
25 score: calculateComplexScore(item)
26 }))
27 .sort((a, b) => b.score - a.score);
28 }, [items, filter]); // Only recalculate when items or filter changes
29
30 // Further filtering based on search term
31 const filteredData = useMemo(() => {
32 if (!searchTerm) return processedData;
33
34 return processedData.filter(item =>
35 item.name.toLowerCase().includes(searchTerm.toLowerCase())
36 );
37 }, [processedData, searchTerm]);
38
39 return (
40 <div>
41 <input
42 type="text"
43 value={searchTerm}
44 onChange={(e) => setSearchTerm(e.target.value)}
45 placeholder="Search items..."
46 />
47
48 <div>
49 {filteredData.map(item => (
50 <div key={item.id}>
51 <h3>{item.name}</h3>
52 <p>Score: {item.score}</p>
53 </div>
54 ))}
55 </div>
56 </div>
57 );
58}
59
60function calculateComplexScore(item) {
61 // Simulate expensive calculation
62 let score = 0;
63 for (let i = 0; i < 10000; i++) {
64 score += Math.random() * item.value;
65 }
66 return Math.round(score);
67}useCallback for Function Memoization:
useCallback memoizes function references to prevent unnecessary re-renders:
1import { useCallback, useState, memo } from 'react';
2
3// Child component that receives callback
4const ChildComponent = memo(({ onUpdate, data }) => {
5 console.log('Child rendered');
6
7 return (
8 <div>
9 <p>{data.name}</p>
10 <button onClick={() => onUpdate(data.id)}>Update</button>
11 </div>
12 );
13});
14
15function ParentComponent({ items }) {
16 const [count, setCount] = useState(0);
17 const [selectedId, setSelectedId] = useState(null);
18
19 // Without useCallback - new function created every render
20 const handleUpdate = (id) => {
21 console.log('Updating item:', id);
22 setSelectedId(id);
23 };
24
25 // With useCallback - same function reference unless dependencies change
26 const memoizedHandleUpdate = useCallback((id) => {
27 console.log('Updating item:', id);
28 setSelectedId(id);
29 }, []);
30
31 // Callback with dependencies
32 const handleUpdateWithDependency = useCallback((id) => {
33 console.log('Updating item:', id, 'Count:', count);
34 setSelectedId(id);
35 }, [count]); // Recreates when count changes
36
37 return (
38 <div>
39 <button onClick={() => setCount(c => c + 1)}>
40 Count: {count}
41 </button>
42
43 {items.map(item => (
44 <ChildComponent
45 key={item.id}
46 data={item}
47 onUpdate={memoizedHandleUpdate}
48 />
49 ))}
50 </div>
51 );
52}Performance Monitoring:
1import { Profiler } from 'react';
2
3function App() {
4 const onRenderCallback = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
5 console.log('Component rendered:', {
6 id,
7 phase, // 'mount' or 'update'
8 actualDuration, // Time spent rendering this update
9 baseDuration, // Estimated time without memoization
10 startTime,
11 commitTime
12 });
13 };
14
15 return (
16 <Profiler id="App" onRender={onRenderCallback}>
17 <Header />
18 <MainContent />
19 <Footer />
20 </Profiler>
21 );
22}Proper error handling prevents crashes and provides graceful user experiences when things go wrong.
Error Boundaries - Catching JavaScript Errors:
Error Boundaries are React components that catch JavaScript errors anywhere in their child component tree:
1import { Component } from 'react';
2
3class ErrorBoundary extends Component {
4 constructor(props) {
5 super(props);
6 this.state = { hasError: false, error: null, errorInfo: null };
7 }
8
9 static getDerivedStateFromError(error) {
10 // Update state to show fallback UI
11 return { hasError: true };
12 }
13
14 componentDidCatch(error, errorInfo) {
15 // Log error details
16 console.error('Error Boundary caught an error:', error, errorInfo);
17
18 this.setState({
19 error: error,
20 errorInfo: errorInfo
21 });
22
23 // Send error to logging service
24 this.logErrorToService(error, errorInfo);
25 }
26
27 logErrorToService = (error, errorInfo) => {
28 // Example: Send to error reporting service
29 // errorReportingService.captureException(error, {
30 // extra: errorInfo,
31 // tags: { component: 'ErrorBoundary' }
32 // });
33 }
34
35 render() {
36 if (this.state.hasError) {
37 // Fallback UI
38 return (
39 <div className="error-boundary">
40 <h2>😨 Something went wrong</h2>
41 <details style={{ whiteSpace: 'pre-wrap' }}>
42 <summary>Error Details (Click to expand)</summary>
43 <p><strong>Error:</strong> {this.state.error && this.state.error.toString()}</p>
44 <p><strong>Component Stack:</strong></p>
45 <code>{this.state.errorInfo.componentStack}</code>
46 </details>
47 <button
48 onClick={() => this.setState({ hasError: false, error: null, errorInfo: null })}
49 >
50 Try Again
51 </button>
52 </div>
53 );
54 }
55
56 return this.props.children;
57 }
58}
59
60// Usage
61function App() {
62 return (
63 <ErrorBoundary>
64 <Header />
65 <ErrorBoundary>
66 <UserProfile />
67 </ErrorBoundary>
68 <ErrorBoundary>
69 <PostList />
70 </ErrorBoundary>
71 </ErrorBoundary>
72 );
73}Modern Error Boundary Hook (Custom Implementation):
1import { useState, useEffect } from 'react';
2
3function useErrorBoundary() {
4 const [error, setError] = useState(null);
5
6 const resetError = () => setError(null);
7
8 const captureError = (error) => {
9 setError(error);
10 };
11
12 useEffect(() => {
13 if (error) {
14 // Log error to service
15 console.error('Error captured:', error);
16 }
17 }, [error]);
18
19 return { error, resetError, captureError };
20}
21
22function ErrorBoundaryWrapper({ children, fallback: Fallback }) {
23 const { error, resetError, captureError } = useErrorBoundary();
24
25 useEffect(() => {
26 const handleError = (event) => {
27 captureError(event.error);
28 };
29
30 const handleUnhandledRejection = (event) => {
31 captureError(event.reason);
32 };
33
34 window.addEventListener('error', handleError);
35 window.addEventListener('unhandledrejection', handleUnhandledRejection);
36
37 return () => {
38 window.removeEventListener('error', handleError);
39 window.removeEventListener('unhandledrejection', handleUnhandledRejection);
40 };
41 }, [captureError]);
42
43 if (error) {
44 return <Fallback error={error} resetError={resetError} />;
45 }
46
47 return children;
48}Async Error Handling:
1import { useState, useEffect } from 'react';
2
3function useAsyncError() {
4 const [error, setError] = useState(null);
5
6 const throwError = (error) => {
7 setError(error);
8 };
9
10 useEffect(() => {
11 if (error) {
12 throw error; // This will be caught by Error Boundary
13 }
14 }, [error]);
15
16 return throwError;
17}
18
19function AsyncDataComponent({ userId }) {
20 const [data, setData] = useState(null);
21 const [loading, setLoading] = useState(true);
22 const throwError = useAsyncError();
23
24 useEffect(() => {
25 async function fetchData() {
26 try {
27 setLoading(true);
28 const response = await fetch(`/api/users/${userId}`);
29
30 if (!response.ok) {
31 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
32 }
33
34 const userData = await response.json();
35 setData(userData);
36 } catch (error) {
37 // This will trigger the Error Boundary
38 throwError(error);
39 } finally {
40 setLoading(false);
41 }
42 }
43
44 fetchData();
45 }, [userId, throwError]);
46
47 if (loading) return <div>Loading...</div>;
48
49 return (
50 <div>
51 <h2>{data.name}</h2>
52 <p>{data.email}</p>
53 </div>
54 );
55}Error Boundary Best Practices:
Well-organized React applications are easier to maintain, test, and scale. Here are proven patterns for structuring React projects.
Folder Structure Patterns:
Feature-Based Structure (Recommended for larger apps):
1src/
2├── components/ # Shared components
3│ ├── Button/
4│ │ ├── Button.jsx
5│ │ ├── Button.test.js
6│ │ └── index.js
7│ └── Modal/
8├── features/ # Feature-specific code
9│ ├── authentication/
10│ │ ├── components/
11│ │ ├── hooks/
12│ │ ├── services/
13│ │ └── index.js
14│ ├── dashboard/
15│ └── profile/
16├── hooks/ # Shared custom hooks
17├── services/ # API calls and external services
18├── utils/ # Utility functions
19├── contexts/ # React contexts
20└── types/ # TypeScript typesDomain-Driven Structure:
1src/
2├── shared/ # Shared across domains
3│ ├── components/
4│ ├── hooks/
5│ └── utils/
6├── domains/
7│ ├── user/
8│ │ ├── User.types.ts
9│ │ ├── UserService.ts
10│ │ ├── UserProfile.jsx
11│ │ └── hooks/
12│ ├── product/
13│ └── order/Component Organization Patterns:
Compound Component Pattern:
1// Card/Card.jsx
2function Card({ children, className = '' }) {
3 return (
4 <div className={`card ${className}`}>
5 {children}
6 </div>
7 );
8}
9
10// Card/CardHeader.jsx
11function CardHeader({ children }) {
12 return <div className="card-header">{children}</div>;
13}
14
15// Card/index.js
16export { default as Card } from './Card';
17export { default as CardHeader } from './CardHeader';
18export { default as CardBody } from './CardBody';
19export { default as CardFooter } from './CardFooter';
20
21// Or attach as properties
22Card.Header = CardHeader;
23Card.Body = CardBody;
24Card.Footer = CardFooter;
25
26export default Card;Custom Hooks for Logic Separation:
1// hooks/useUserData.js
2import { useState, useEffect } from 'react';
3import { userService } from '../services/userService';
4
5export function useUserData(userId) {
6 const [user, setUser] = useState(null);
7 const [loading, setLoading] = useState(true);
8 const [error, setError] = useState(null);
9
10 useEffect(() => {
11 let cancelled = false;
12
13 async function fetchUser() {
14 try {
15 setLoading(true);
16 const userData = await userService.getUser(userId);
17
18 if (!cancelled) {
19 setUser(userData);
20 setError(null);
21 }
22 } catch (err) {
23 if (!cancelled) {
24 setError(err);
25 setUser(null);
26 }
27 } finally {
28 if (!cancelled) {
29 setLoading(false);
30 }
31 }
32 }
33
34 if (userId) {
35 fetchUser();
36 }
37
38 return () => {
39 cancelled = true;
40 };
41 }, [userId]);
42
43 const refetch = () => {
44 if (userId) {
45 fetchUser();
46 }
47 };
48
49 return { user, loading, error, refetch };
50}
51
52// Usage in component
53function UserProfile({ userId }) {
54 const { user, loading, error, refetch } = useUserData(userId);
55
56 if (loading) return <div>Loading...</div>;
57 if (error) return <div>Error: {error.message}</div>;
58 if (!user) return <div>User not found</div>;
59
60 return (
61 <div>
62 <h2>{user.name}</h2>
63 <p>{user.email}</p>
64 <button onClick={refetch}>Refresh</button>
65 </div>
66 );
67}Service Layer Pattern:
1// services/api.js
2class ApiService {
3 constructor(baseURL) {
4 this.baseURL = baseURL;
5 }
6
7 async request(endpoint, options = {}) {
8 const url = `${this.baseURL}${endpoint}`;
9
10 const config = {
11 headers: {
12 'Content-Type': 'application/json',
13 ...options.headers,
14 },
15 ...options,
16 };
17
18 if (config.body && typeof config.body === 'object') {
19 config.body = JSON.stringify(config.body);
20 }
21
22 const response = await fetch(url, config);
23
24 if (!response.ok) {
25 const error = await response.json().catch(() => ({}));
26 throw new Error(error.message || `HTTP ${response.status}`);
27 }
28
29 return response.json();
30 }
31
32 get(endpoint) {
33 return this.request(endpoint, { method: 'GET' });
34 }
35
36 post(endpoint, data) {
37 return this.request(endpoint, { method: 'POST', body: data });
38 }
39
40 put(endpoint, data) {
41 return this.request(endpoint, { method: 'PUT', body: data });
42 }
43
44 delete(endpoint) {
45 return this.request(endpoint, { method: 'DELETE' });
46 }
47}
48
49export const api = new ApiService(process.env.REACT_APP_API_URL);
50
51// services/userService.js
52import { api } from './api';
53
54export const userService = {
55 getUser: (id) => api.get(`/users/${id}`),
56 updateUser: (id, data) => api.put(`/users/${id}`, data),
57 deleteUser: (id) => api.delete(`/users/${id}`),
58 getUserPosts: (id) => api.get(`/users/${id}/posts`),
59};Context Organization:
1// contexts/AuthContext.js
2import { createContext, useContext, useReducer, useEffect } from 'react';
3
4const AuthContext = createContext();
5
6const authReducer = (state, action) => {
7 switch (action.type) {
8 case 'LOGIN_SUCCESS':
9 return { ...state, user: action.payload, isAuthenticated: true };
10 case 'LOGOUT':
11 return { ...state, user: null, isAuthenticated: false };
12 case 'UPDATE_USER':
13 return { ...state, user: { ...state.user, ...action.payload } };
14 default:
15 return state;
16 }
17};
18
19export function AuthProvider({ children }) {
20 const [state, dispatch] = useReducer(authReducer, {
21 user: null,
22 isAuthenticated: false,
23 });
24
25 const login = async (credentials) => {
26 try {
27 const user = await authService.login(credentials);
28 dispatch({ type: 'LOGIN_SUCCESS', payload: user });
29 } catch (error) {
30 throw error;
31 }
32 };
33
34 const logout = () => {
35 authService.logout();
36 dispatch({ type: 'LOGOUT' });
37 };
38
39 const updateUser = (userData) => {
40 dispatch({ type: 'UPDATE_USER', payload: userData });
41 };
42
43 return (
44 <AuthContext.Provider value={{
45 ...state,
46 login,
47 logout,
48 updateUser,
49 }}>
50 {children}
51 </AuthContext.Provider>
52 );
53}
54
55export const useAuth = () => {
56 const context = useContext(AuthContext);
57 if (!context) {
58 throw new Error('useAuth must be used within an AuthProvider');
59 }
60 return context;
61};Mastering React development tools and debugging techniques accelerates development and helps solve complex issues quickly.
React Developer Tools:
The React DevTools browser extension provides powerful debugging capabilities:
Component Tree Inspection:
Profiler for Performance Analysis:
1// Enable profiling in development
2import { unstable_trace as trace } from 'scheduler/tracing';
3
4function ExpensiveComponent() {
5 return trace('ExpensiveComponent render', performance.now(), () => {
6 // Component logic here
7 return <div>Expensive computation result</div>;
8 });
9}Console Debugging Techniques:
1import { useEffect, useRef } from 'react';
2
3// Custom hook for debugging props changes
4function useWhyDidYouUpdate(name, props) {
5 const previous = useRef();
6
7 useEffect(() => {
8 if (previous.current) {
9 const allKeys = Object.keys({ ...previous.current, ...props });
10 const changedProps = {};
11
12 allKeys.forEach(key => {
13 if (previous.current[key] !== props[key]) {
14 changedProps[key] = {
15 from: previous.current[key],
16 to: props[key],
17 };
18 }
19 });
20
21 if (Object.keys(changedProps).length) {
22 console.log('[why-did-you-update]', name, changedProps);
23 }
24 }
25
26 previous.current = props;
27 });
28}
29
30// Usage
31function MyComponent(props) {
32 useWhyDidYouUpdate('MyComponent', props);
33 return <div>{props.children}</div>;
34}
35
36// Debug renders
37function useRenderCount(componentName) {
38 const renderCount = useRef(0);
39
40 useEffect(() => {
41 renderCount.current++;
42 console.log(`${componentName} rendered ${renderCount.current} times`);
43 });
44
45 return renderCount.current;
46}Error Logging and Monitoring:
1// Development error logging
2function setupErrorLogging() {
3 if (process.env.NODE_ENV === 'development') {
4 // Console warnings for common mistakes
5 const originalConsoleError = console.error;
6 console.error = (...args) => {
7 if (args[0] && args[0].includes('Warning:')) {
8 // React warnings in development
9 console.group('⚠️ React Warning');
10 originalConsoleError(...args);
11 console.trace('Component stack trace:');
12 console.groupEnd();
13 } else {
14 originalConsoleError(...args);
15 }
16 };
17 }
18
19 // Global error handlers
20 window.addEventListener('error', (event) => {
21 console.error('Global error:', event.error);
22 // Send to error reporting service in production
23 });
24
25 window.addEventListener('unhandledrejection', (event) => {
26 console.error('Unhandled promise rejection:', event.reason);
27 // Send to error reporting service in production
28 });
29}
30
31// Call during app initialization
32setupErrorLogging();Testing Utilities:
1import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2import { jest } from '@jest/globals';
3
4// Debug component during tests
5function debugComponent(component) {
6 const { debug } = render(component);
7 debug(); // Prints current DOM state
8}
9
10// Custom render with providers
11function renderWithProviders(ui, options = {}) {
12 const AllProviders = ({ children }) => (
13 <AuthProvider>
14 <ThemeProvider>
15 {children}
16 </ThemeProvider>
17 </AuthProvider>
18 );
19
20 return render(ui, { wrapper: AllProviders, ...options });
21}
22
23// Test example
24test('UserProfile displays user information', async () => {
25 const mockUser = { id: 1, name: 'John Doe', email: '[email protected]' };
26
27 // Mock API call
28 jest.spyOn(userService, 'getUser').mockResolvedValue(mockUser);
29
30 renderWithProviders(<UserProfile userId={1} />);
31
32 // Debug if needed
33 screen.debug();
34
35 await waitFor(() => {
36 expect(screen.getByText('John Doe')).toBeInTheDocument();
37 });
38
39 expect(screen.getByText('[email protected]')).toBeInTheDocument();
40});Performance Debugging:
1// Performance monitoring hook
2function usePerformanceMonitor(componentName) {
3 useEffect(() => {
4 const startTime = performance.now();
5
6 return () => {
7 const endTime = performance.now();
8 const renderTime = endTime - startTime;
9
10 if (renderTime > 16) { // More than one frame (60fps)
11 console.warn(
12 `🐌 ${componentName} took ${renderTime.toFixed(2)}ms to render (>16ms)`
13 );
14 }
15 };
16 });
17}
18
19// Bundle analysis
20// Run: npm run build && npx webpack-bundle-analyzer build/static/js/*.jsDevelopment Environment Setup:
1// .env.development
2REACT_APP_ENV=development
3REACT_APP_API_URL=http://localhost:3001/api
4REACT_APP_LOG_LEVEL=debug
5
6// config/development.js
7export const config = {
8 apiUrl: process.env.REACT_APP_API_URL,
9 logLevel: process.env.REACT_APP_LOG_LEVEL,
10 enableDevTools: true,
11 enableReduxLogger: true,
12 enableWhyDidYouRender: true,
13};
14
15// Conditional development features
16if (process.env.NODE_ENV === 'development') {
17 // Enable Why Did You Render
18 const whyDidYouRender = require('@welldone-software/why-did-you-render');
19 whyDidYouRender(React, {
20 trackAllPureComponents: true,
21 });
22}Build a comprehensive React application demonstrating all best practices: performance optimization, error handling, proper architecture, and development tooling.
1// Production-Ready React Application
2import React, {
3 Component,
4 memo,
5 useMemo,
6 useCallback,
7 useState,
8 useEffect,
9 createContext,
10 useContext,
11 Suspense,
12 lazy
13} from 'react';
14
15// 1. Error Boundary Implementation
16class ErrorBoundary extends Component {
17 constructor(props) {
18 super(props);
19 this.state = { hasError: false, error: null };
20 }
21
22 static getDerivedStateFromError(error) {
23 return { hasError: true, error };
24 }
25
26 componentDidCatch(error, errorInfo) {
27 console.error('Error Boundary:', error, errorInfo);
28
29 // In production, send to error reporting service
30 if (process.env.NODE_ENV === 'production') {
31 // errorService.captureException(error, errorInfo);
32 }
33 }
34
35 render() {
36 if (this.state.hasError) {
37 return (
38 <div className="error-boundary p-6 bg-red-50 border border-red-200 rounded-lg">
39 <h2 className="text-xl font-bold text-red-800 mb-2">
40 😱 Something went wrong
41 </h2>
42 <p className="text-red-600 mb-4">
43 An unexpected error occurred. Our team has been notified.
44 </p>
45 <button
46 onClick={() => this.setState({ hasError: false, error: null })}
47 className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
48 >
49 Try Again
50 </button>
51
52 {process.env.NODE_ENV === 'development' && (
53 <details className="mt-4">
54 <summary className="cursor-pointer text-sm">Error Details</summary>
55 <pre className="mt-2 p-2 bg-red-100 text-xs overflow-auto">
56 {this.state.error?.toString()}
57 </pre>
58 </details>
59 )}
60 </div>
61 );
62 }
63
64 return this.props.children;
65 }
66}
67
68// 2. Performance-Optimized Components
69const UserCard = memo(({ user, onUpdate, onDelete }) => {
70 console.log(`UserCard rendered for user: ${user.name}`);
71
72 return (
73 <div className="bg-white p-4 rounded-lg shadow-md">
74 <div className="flex items-center space-x-3">
75 <img
76 src={user.avatar}
77 alt={user.name}
78 className="w-12 h-12 rounded-full"
79 />
80 <div className="flex-1">
81 <h3 className="font-semibold">{user.name}</h3>
82 <p className="text-gray-600 text-sm">{user.email}</p>
83 <p className="text-xs text-gray-500">
84 Last active: {new Date(user.lastActive).toLocaleDateString()}
85 </p>
86 </div>
87 <div className="space-x-2">
88 <button
89 onClick={() => onUpdate(user.id)}
90 className="px-3 py-1 bg-blue-500 text-white text-sm rounded hover:bg-blue-600"
91 >
92 Edit
93 </button>
94 <button
95 onClick={() => onDelete(user.id)}
96 className="px-3 py-1 bg-red-500 text-white text-sm rounded hover:bg-red-600"
97 >
98 Delete
99 </button>
100 </div>
101 </div>
102 </div>
103 );
104});
105
106UserCard.displayName = 'UserCard';
107
108// 3. Custom Hooks with Error Handling
109function useAsyncOperation() {
110 const [loading, setLoading] = useState(false);
111 const [error, setError] = useState(null);
112
113 const execute = useCallback(async (asyncFunction) => {
114 try {
115 setLoading(true);
116 setError(null);
117 const result = await asyncFunction();
118 return result;
119 } catch (err) {
120 setError(err);
121 throw err;
122 } finally {
123 setLoading(false);
124 }
125 }, []);
126
127 return { loading, error, execute };
128}
129
130function useUserData() {
131 const [users, setUsers] = useState([]);
132 const { loading, error, execute } = useAsyncOperation();
133
134 // Memoized API functions
135 const fetchUsers = useCallback(async () => {
136 const response = await fetch('/api/users');
137 if (!response.ok) throw new Error('Failed to fetch users');
138 return response.json();
139 }, []);
140
141 const updateUser = useCallback(async (userId, userData) => {
142 const response = await fetch(`/api/users/${userId}`, {
143 method: 'PUT',
144 headers: { 'Content-Type': 'application/json' },
145 body: JSON.stringify(userData)
146 });
147 if (!response.ok) throw new Error('Failed to update user');
148 return response.json();
149 }, []);
150
151 const deleteUser = useCallback(async (userId) => {
152 const response = await fetch(`/api/users/${userId}`, {
153 method: 'DELETE'
154 });
155 if (!response.ok) throw new Error('Failed to delete user');
156 }, []);
157
158 // Load users on mount
159 useEffect(() => {
160 execute(fetchUsers).then(setUsers).catch(console.error);
161 }, [execute, fetchUsers]);
162
163 return {
164 users,
165 loading,
166 error,
167 updateUser: (userId, userData) => execute(() => updateUser(userId, userData)),
168 deleteUser: (userId) => execute(() => deleteUser(userId)),
169 refetch: () => execute(fetchUsers).then(setUsers)
170 };
171}
172
173// 4. Context for Global State
174const AppContext = createContext();
175
176function AppProvider({ children }) {
177 const [theme, setTheme] = useState('light');
178 const [user, setUser] = useState(null);
179
180 const toggleTheme = useCallback(() => {
181 setTheme(prev => prev === 'light' ? 'dark' : 'light');
182 }, []);
183
184 const value = useMemo(() => ({
185 theme,
186 user,
187 setUser,
188 toggleTheme
189 }), [theme, user, toggleTheme]);
190
191 return (
192 <AppContext.Provider value={value}>
193 {children}
194 </AppContext.Provider>
195 );
196}
197
198const useApp = () => {
199 const context = useContext(AppContext);
200 if (!context) {
201 throw new Error('useApp must be used within AppProvider');
202 }
203 return context;
204};
205
206// 5. Main Application Components
207function UserManagement() {
208 const { users, loading, error, updateUser, deleteUser } = useUserData();
209 const [searchTerm, setSearchTerm] = useState('');
210
211 // Memoized filtered users
212 const filteredUsers = useMemo(() => {
213 if (!searchTerm) return users;
214
215 return users.filter(user =>
216 user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
217 user.email.toLowerCase().includes(searchTerm.toLowerCase())
218 );
219 }, [users, searchTerm]);
220
221 // Memoized callbacks to prevent unnecessary re-renders
222 const handleUpdate = useCallback(async (userId) => {
223 try {
224 await updateUser(userId, { lastActive: new Date().toISOString() });
225 console.log('User updated successfully');
226 } catch (err) {
227 console.error('Update failed:', err);
228 }
229 }, [updateUser]);
230
231 const handleDelete = useCallback(async (userId) => {
232 if (!window.confirm('Are you sure you want to delete this user?')) {
233 return;
234 }
235
236 try {
237 await deleteUser(userId);
238 console.log('User deleted successfully');
239 } catch (err) {
240 console.error('Delete failed:', err);
241 }
242 }, [deleteUser]);
243
244 if (loading) {
245 return (
246 <div className="flex items-center justify-center p-8">
247 <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
248 <span className="ml-2">Loading users...</span>
249 </div>
250 );
251 }
252
253 if (error) {
254 return (
255 <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
256 <p className="text-red-800">Error loading users: {error.message}</p>
257 </div>
258 );
259 }
260
261 return (
262 <div className="space-y-6">
263 <div className="flex items-center justify-between">
264 <h2 className="text-2xl font-bold">User Management</h2>
265 <div className="flex items-center space-x-4">
266 <input
267 type="text"
268 value={searchTerm}
269 onChange={(e) => setSearchTerm(e.target.value)}
270 placeholder="Search users..."
271 className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
272 />
273 <span className="text-sm text-gray-600">
274 {filteredUsers.length} users
275 </span>
276 </div>
277 </div>
278
279 <div className="space-y-4">
280 {filteredUsers.map(user => (
281 <UserCard
282 key={user.id}
283 user={user}
284 onUpdate={handleUpdate}
285 onDelete={handleDelete}
286 />
287 ))}
288 </div>
289
290 {filteredUsers.length === 0 && searchTerm && (
291 <div className="text-center py-8 text-gray-500">
292 No users found matching "{searchTerm}"
293 </div>
294 )}
295 </div>
296 );
297}
298
299// 6. Header Component with Theme Toggle
300function Header() {
301 const { theme, toggleTheme } = useApp();
302
303 return (
304 <header className="bg-white shadow-sm border-b border-gray-200 px-6 py-4">
305 <div className="flex items-center justify-between">
306 <h1 className="text-xl font-bold text-gray-800">
307 React Best Practices Demo
308 </h1>
309
310 <div className="flex items-center space-x-4">
311 <button
312 onClick={toggleTheme}
313 className="px-3 py-2 border border-gray-300 rounded-md hover:bg-gray-50"
314 >
315 {theme === 'light' ? '🌙' : '☀️'}
316 {theme === 'light' ? 'Dark' : 'Light'} Mode
317 </button>
318
319 <div className="text-sm text-gray-600">
320 Environment: {process.env.NODE_ENV}
321 </div>
322 </div>
323 </div>
324 </header>
325 );
326}
327
328// 7. Performance Monitoring Component
329function PerformanceMonitor({ children }) {
330 useEffect(() => {
331 // Monitor long tasks
332 if ('PerformanceObserver' in window) {
333 const observer = new PerformanceObserver((list) => {
334 for (const entry of list.getEntries()) {
335 if (entry.duration > 50) {
336 console.warn(`Long task detected: ${entry.duration}ms`);
337 }
338 }
339 });
340
341 observer.observe({ entryTypes: ['longtask'] });
342
343 return () => observer.disconnect();
344 }
345 }, []);
346
347 return children;
348}
349
350// 8. Main App Component
351function App() {
352 const [showUserManagement, setShowUserManagement] = useState(true);
353
354 return (
355 <ErrorBoundary>
356 <AppProvider>
357 <PerformanceMonitor>
358 <div className="min-h-screen bg-gray-100">
359 <Header />
360
361 <main className="max-w-6xl mx-auto p-6">
362 <div className="mb-6">
363 <button
364 onClick={() => setShowUserManagement(!showUserManagement)}
365 className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
366 >
367 {showUserManagement ? 'Hide' : 'Show'} User Management
368 </button>
369 </div>
370
371 {showUserManagement && (
372 <ErrorBoundary>
373 <Suspense fallback={<div>Loading user management...</div>}>
374 <UserManagement />
375 </Suspense>
376 </ErrorBoundary>
377 )}
378
379 {/* Best Practices Summary */}
380 <div className="mt-8 p-6 bg-white rounded-lg shadow-md">
381 <h3 className="text-lg font-bold mb-4">🚀 Best Practices Demonstrated</h3>
382
383 <div className="grid md:grid-cols-2 gap-6">
384 <div>
385 <h4 className="font-semibold mb-2">✅ Performance Optimizations:</h4>
386 <ul className="text-sm space-y-1 text-gray-700">
387 <li>• React.memo for component memoization</li>
388 <li>• useMemo for expensive calculations</li>
389 <li>• useCallback for function memoization</li>
390 <li>• Lazy loading with React.lazy</li>
391 <li>• Performance monitoring</li>
392 </ul>
393 </div>
394
395 <div>
396 <h4 className="font-semibold mb-2">🛡️ Error Handling:</h4>
397 <ul className="text-sm space-y-1 text-gray-700">
398 <li>• Error boundaries for crash prevention</li>
399 <li>• Async error handling in hooks</li>
400 <li>• Graceful fallback UIs</li>
401 <li>• Development vs production error display</li>
402 <li>• Error logging and monitoring</li>
403 </ul>
404 </div>
405
406 <div>
407 <h4 className="font-semibold mb-2">🏗️ Architecture:</h4>
408 <ul className="text-sm space-y-1 text-gray-700">
409 <li>• Custom hooks for logic separation</li>
410 <li>• Context for global state management</li>
411 <li>• Proper component composition</li>
412 <li>• Separation of concerns</li>
413 <li>• Reusable component patterns</li>
414 </ul>
415 </div>
416
417 <div>
418 <h4 className="font-semibold mb-2">🔧 Development:</h4>
419 <ul className="text-sm space-y-1 text-gray-700">
420 <li>• Environment-specific configurations</li>
421 <li>• Component debugging techniques</li>
422 <li>• Performance monitoring</li>
423 <li>• Type-safe prop handling</li>
424 <li>• Testing-friendly structure</li>
425 </ul>
426 </div>
427 </div>
428 </div>
429 </main>
430 </div>
431 </PerformanceMonitor>
432 </AppProvider>
433 </ErrorBoundary>
434 );
435}
436
437export default App;
438
439// Mock data for demonstration (replace with real API)
440if (process.env.NODE_ENV === 'development') {
441 // Mock API responses
442 const mockUsers = [
443 {
444 id: 1,
445 name: 'Alice Johnson',
446 email: '[email protected]',
447 avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=150',
448 lastActive: new Date().toISOString()
449 },
450 {
451 id: 2,
452 name: 'Bob Smith',
453 email: '[email protected]',
454 avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150',
455 lastActive: new Date(Date.now() - 86400000).toISOString()
456 },
457 {
458 id: 3,
459 name: 'Carol Williams',
460 email: '[email protected]',
461 avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150',
462 lastActive: new Date(Date.now() - 172800000).toISOString()
463 }
464 ];
465
466 // Mock fetch globally for demonstration
467 const originalFetch = window.fetch;
468 window.fetch = async (url, options) => {
469 console.log('Mock API call:', url, options?.method || 'GET');
470
471 // Simulate network delay
472 await new Promise(resolve => setTimeout(resolve, 500));
473
474 if (url === '/api/users') {
475 return Promise.resolve({
476 ok: true,
477 json: () => Promise.resolve(mockUsers)
478 });
479 }
480
481 // For other endpoints, use original fetch or return mock error
482 return originalFetch ? originalFetch(url, options) : Promise.reject(new Error('Not found'));
483 };
484}This production-ready React application demonstrates enterprise-level best practices:
Performance Optimizations:
React.memo prevents unnecessary re-renders of UserCard componentsuseMemo optimizes expensive filtering operationsuseCallback prevents function recreation on every renderError Handling:
useAsyncOperation provides consistent async error handlingArchitecture Best Practices:
Development Experience:
Production Readiness:
This architecture scales from small projects to enterprise applications while maintaining code quality, performance, and developer productivity.
Congratulations! You've completed React Fundamentals and mastered production-ready development practices. Your next steps:
You now have the skills to build professional React applications that scale, perform well, and are maintainable by development teams.
Your progress is saved in this browser, on this device only.