โš›๏ธโšก๏ธ React Performance Optimization: Complete Guide to useMemo and useCallback

React's performance optimization hooks useMemo and useCallback are essential tools for building efficient applications, yet they're often misunderstood and overused. This comprehensive guide will teach you when, why, and how to use these hooks effectively, with real-world examples and best practices.

๐Ÿง  Understanding React's Rendering Fundamentals

Before diving into performance hooks, it's crucial to understand how React works. Every time your component's state or props change, React triggers a re-render process:

  1. โš™๏ธ Function Execution: Your component function runs from top to bottom
  2. ๐Ÿงฑ Virtual DOM Creation: React creates a new virtual representation
  3. ๐Ÿ” Diffing: React compares the new virtual DOM with the previous version
  4. ๐Ÿงผ DOM Updates: Only changed elements are updated in the real DOM

๐Ÿ‘‰ The key insight is that every variable, function, and object is recreated on each render, regardless of whether their values have actually changed.

jsx
1const UserProfile = ({ userId }) => { 2 const [user, setUser] = useState(null); 3 const [preferences, setPreferences] = useState({}); 4 5 // These are ALL recreated on every render ๐Ÿ”„ 6 const userSettings = { theme: 'dark', language: 'en' }; 7 const handleSave = () => saveUserData(user); 8 const fullName = user ? `${user.firstName} ${user.lastName}` : ''; 9 10 return ( 11 <div> 12 <h1>{fullName}</h1> 13 <Settings config={userSettings} /> 14 <SaveButton onClick={handleSave} /> 15 </div> 16 ); 17};

๐Ÿšจ Problem 1: Expensive Calculations

Consider this real-world example of a data analytics dashboard:

jsx
1const SalesAnalytics = () => { 2 const [salesData, setSalesData] = useState([]); 3 const [dateRange, setDateRange] = useState({ start: null, end: null }); 4 const [refreshCounter, setRefreshCounter] = useState(0); 5 6 // This expensive calculation runs on EVERY render ๐Ÿ’ธ 7 const analytics = calculateSalesMetrics(salesData, dateRange); 8 9 // Auto-refresh every 30 seconds โฐ 10 useEffect(() => { 11 const interval = setInterval(() => { 12 setRefreshCounter((prev) => prev + 1); 13 }, 30000); 14 return () => clearInterval(interval); 15 }, []); 16 17 return ( 18 <div> 19 <p>๐Ÿ“Š Last updated: {new Date().toLocaleTimeString()}</p> 20 <MetricsDisplay data={analytics} /> 21 <DateRangeSelector onChange={setDateRange} /> 22 </div> 23 ); 24}; 25 26const calculateSalesMetrics = (data, dateRange) => { 27 console.log('๐Ÿ”„ Calculating expensive metrics...'); 28 29 // Simulate expensive operations ๐Ÿš€ 30 const filtered = data.filter((sale) => { 31 const saleDate = new Date(sale.date); 32 return ( 33 (!dateRange.start || saleDate >= dateRange.start) && 34 (!dateRange.end || saleDate <= dateRange.end) 35 ); 36 }); 37 38 return { 39 totalRevenue: filtered.reduce((sum, sale) => sum + sale.amount, 0), 40 averageOrderValue: 41 filtered.reduce((sum, sale) => sum + sale.amount, 0) / filtered.length, 42 topProducts: getTopProducts(filtered), 43 monthlyTrends: calculateTrends(filtered), 44 }; 45};

โš ๏ธ The Problem: The expensive calculateSalesMetrics function runs every 30 seconds when refreshCounter updates, even though salesData and dateRange haven't changed.

๐Ÿ’ก Solution 1: useMemo for Expensive Calculations

jsx
1const SalesAnalytics = () => { 2 const [salesData, setSalesData] = useState([]); 3 const [dateRange, setDateRange] = useState({ start: null, end: null }); 4 const [refreshCounter, setRefreshCounter] = useState(0); 5 6 // Only recalculate when salesData or dateRange change ๐ŸŽฏ 7 const analytics = useMemo(() => { 8 console.log('๐Ÿ”„ Calculating metrics...'); // You'll see this much less now ๐Ÿ‘ 9 return calculateSalesMetrics(salesData, dateRange); 10 }, [salesData, dateRange]); 11 12 // Auto-refresh logic remains the same โฐ 13 useEffect(() => { 14 const interval = setInterval(() => { 15 setRefreshCounter((prev) => prev + 1); 16 }, 30000); 17 return () => clearInterval(interval); 18 }, []); 19 20 return ( 21 <div> 22 <p>๐Ÿ“Š Last updated: {new Date().toLocaleTimeString()}</p> 23 <MetricsDisplay data={analytics} /> 24 <DateRangeSelector onChange={setDateRange} /> 25 </div> 26 ); 27};

๐Ÿ“Œ Now the expensive calculation only runs when the actual dependencies change, not on every render.

๐Ÿšจ Problem 2: Reference Equality and Memoized Components

Here's a common scenario with an e-commerce shopping cart:

jsx
1const ShoppingCart = () => { 2 const [items, setItems] = useState([]); 3 const [customerInfo, setCustomerInfo] = useState({}); 4 const [promoCode, setPromoCode] = useState(''); 5 6 // This object is recreated on every render ๐Ÿ†• 7 const cartSummary = { 8 itemCount: items.length, 9 subtotal: items.reduce((sum, item) => sum + item.price * item.quantity, 0), 10 tax: 0, 11 total: 0, 12 }; 13 14 // Calculate tax and total ๐Ÿงฎ 15 cartSummary.tax = cartSummary.subtotal * 0.08; 16 cartSummary.total = cartSummary.subtotal + cartSummary.tax; 17 18 const handleRemoveItem = (itemId) => { 19 setItems((prev) => prev.filter((item) => item.id !== itemId)); 20 }; 21 22 return ( 23 <div> 24 <ItemList items={items} onRemoveItem={handleRemoveItem} /> 25 <CartSummary summary={cartSummary} /> 26 <PromoCodeInput value={promoCode} onChange={setPromoCode} /> 27 </div> 28 ); 29}; 30 31// This component should only re-render when summary changes ๐ŸŽฏ 32const CartSummary = React.memo(({ summary }) => { 33 console.log('๐Ÿ”„ CartSummary rendered'); 34 return ( 35 <div className="cart-summary"> 36 <h3>๐Ÿ“‹ Order Summary</h3> 37 <p>๐Ÿ“ฆ Items: {summary.itemCount}</p> 38 <p>๐Ÿ’ฐ Subtotal: ${summary.subtotal.toFixed(2)}</p> 39 <p>๐Ÿท๏ธ Tax: ${summary.tax.toFixed(2)}</p> 40 <p>๐Ÿ’ณ Total: ${summary.total.toFixed(2)}</p> 41 </div> 42 ); 43});

โš ๏ธ The Problem: Even though CartSummary is wrapped in React.memo, it re-renders whenever promoCode changes because carrtSummary is a new object reference every time.

๐Ÿ’ก Solution 2: useMemo for Reference Stability

jsx
1const ShoppingCart = () => { 2 const [items, setItems] = useState([]); 3 const [customerInfo, setCustomerInfo] = useState({}); 4 const [promoCode, setPromoCode] = useState(''); 5 6 // Memoize the cart summary calculation ๐ŸŽฏ 7 const cartSummary = useMemo(() => { 8 const subtotal = items.reduce( 9 (sum, item) => sum + item.price * item.quantity, 10 0 11 ); 12 const tax = subtotal * 0.08; 13 14 return { 15 itemCount: items.length, 16 subtotal, 17 tax, 18 total: subtotal + tax, 19 }; 20 }, [items]); // Only recalculate when items change โœ… 21 22 const handleRemoveItem = (itemId) => { 23 setItems((prev) => prev.filter((item) => item.id !== itemId)); 24 }; 25 26 return ( 27 <div> 28 <ItemList items={items} onRemoveItem={handleRemoveItem} /> 29 <CartSummary summary={cartSummary} /> 30 <PromoCodeInput value={promoCode} onChange={setPromoCode} /> 31 </div> 32 ); 33};

๐ŸงŠ Now CartSummary only re-renders when the cart items actually change, not when the promo code is typed.

๐Ÿšจ Problem 3: Function References and useCallback

Functions suffer from the same reference problem. Here's a task management example:

jsx
1const TaskManager = () => { 2 const [tasks, setTasks] = useState([]); 3 const [filter, setFilter] = useState('all'); 4 const [searchTerm, setSearchTerm] = useState(''); 5 6 // These functions are recreated on every render ๐Ÿ†• 7 const addTask = (text) => { 8 const newTask = { 9 id: Date.now(), 10 text, 11 completed: false, 12 createdAt: new Date(), 13 }; 14 setTasks((prev) => [...prev, newTask]); 15 }; 16 17 const toggleTask = (id) => { 18 setTasks((prev) => 19 prev.map((task) => 20 task.id === id ? { ...task, completed: !task.completed } : task 21 ) 22 ); 23 }; 24 25 const deleteTask = (id) => { 26 setTasks((prev) => prev.filter((task) => task.id !== id)); 27 }; 28 29 // Filter tasks based on current filter and search term ๐Ÿ” 30 const filteredTasks = useMemo(() => { 31 return tasks.filter((task) => { 32 const matchesFilter = 33 filter === 'all' || 34 (filter === 'completed' && task.completed) || 35 (filter === 'pending' && !task.completed); 36 37 const matchesSearch = task.text 38 .toLowerCase() 39 .includes(searchTerm.toLowerCase()); 40 41 return matchesFilter && matchesSearch; 42 }); 43 }, [tasks, filter, searchTerm]); 44 45 return ( 46 <div> 47 <TaskInput onAddTask={addTask} /> 48 <TaskFilters filter={filter} onFilterChange={setFilter} /> 49 <SearchInput value={searchTerm} onChange={setSearchTerm} /> 50 <TaskList 51 tasks={filteredTasks} 52 onToggle={toggleTask} 53 onDelete={deleteTask} 54 /> 55 </div> 56 ); 57}; 58 59// These components are memoized but still re-render unnecessarily ๐Ÿ˜” 60const TaskInput = React.memo(({ onAddTask }) => { 61 const [input, setInput] = useState(''); 62 63 const handleSubmit = (e) => { 64 e.preventDefault(); 65 if (input.trim()) { 66 onAddTask(input.trim()); 67 setInput(''); 68 } 69 }; 70 71 return ( 72 <form onSubmit={handleSubmit}> 73 <input 74 value={input} 75 onChange={(e) => setInput(e.target.value)} 76 placeholder="โž• Add a new task..." 77 /> 78 <button type="submit">Add</button> 79 </form> 80 ); 81}); 82 83const TaskList = React.memo(({ tasks, onToggle, onDelete }) => { 84 console.log('๐Ÿ”„ TaskList rendered'); 85 return ( 86 <div> 87 {tasks.map((task) => ( 88 <TaskItem 89 key={task.id} 90 task={task} 91 onToggle={onToggle} 92 onDelete={onDelete} 93 /> 94 ))} 95 </div> 96 ); 97});

โš ๏ธ The Problem: TaskInput and TaskList re-render whenever any state changes because their function props are new references each time.

๐Ÿ’ก Solution 3: useCallback for Function Stability

jsx
1const TaskManager = () => { 2 const [tasks, setTasks] = useState([]); 3 const [filter, setFilter] = useState('all'); 4 const [searchTerm, setSearchTerm] = useState(''); 5 6 // Memoize functions with useCallback ๐ŸŽฏ 7 const addTask = useCallback((text) => { 8 const newTask = { 9 id: Date.now(), 10 text, 11 completed: false, 12 createdAt: new Date(), 13 }; 14 setTasks((prev) => [...prev, newTask]); 15 }, []); // Empty dependency array because we use functional update โœ… 16 17 const toggleTask = useCallback((id) => { 18 setTasks((prev) => 19 prev.map((task) => 20 task.id === id ? { ...task, completed: !task.completed } : task 21 ) 22 ); 23 }, []); // Empty dependency array because we use functional update โœ… 24 25 const deleteTask = useCallback((id) => { 26 setTasks((prev) => prev.filter((task) => task.id !== id)); 27 }, []); // Empty dependency array because we use functional update โœ… 28 29 // Filter tasks (using useMemo as before) ๐Ÿ” 30 const filteredTasks = useMemo(() => { 31 return tasks.filter((task) => { 32 const matchesFilter = 33 filter === 'all' || 34 (filter === 'completed' && task.completed) || 35 (filter === 'pending' && !task.completed); 36 37 const matchesSearch = task.text 38 .toLowerCase() 39 .includes(searchTerm.toLowerCase()); 40 41 return matchesFilter && matchesSearch; 42 }); 43 }, [tasks, filter, searchTerm]); 44 45 return ( 46 <div> 47 <TaskInput onAddTask={addTask} /> 48 <TaskFilters filter={filter} onFilterChange={setFilter} /> 49 <SearchInput value={searchTerm} onChange={setSearchTerm} /> 50 <TaskList 51 tasks={filteredTasks} 52 onToggle={toggleTask} 53 onDelete={deleteTask} 54 /> 55 </div> 56 ); 57};

๐Ÿ’ก Key Insight: By using functional updates (prev => ...), we avoid including current state in the dependency array, making our callbacks more stable.

๐Ÿš€ Advanced Example: Complex Data Processing

Here's a comprehensive example showing both hooks working together in a data visualization dashboard:

jsx
1const DataVisualization = () => { 2 const [rawData, setRawData] = useState([]); 3 const [chartType, setChartType] = useState('line'); 4 const [dateRange, setDateRange] = useState({ start: null, end: null }); 5 const [groupBy, setGroupBy] = useState('day'); 6 const [selectedMetrics, setSelectedMetrics] = useState(['revenue', 'users']); 7 8 // Complex data processing with useMemo ๐Ÿ”„ 9 const processedData = useMemo(() => { 10 console.log('๐Ÿ”„ Processing chart data...'); 11 12 // Filter by date range ๐Ÿ“… 13 const filteredData = rawData.filter((item) => { 14 const date = new Date(item.timestamp); 15 return ( 16 (!dateRange.start || date >= dateRange.start) && 17 (!dateRange.end || date <= dateRange.end) 18 ); 19 }); 20 21 // Group data by specified period ๐Ÿ“Š 22 const grouped = filteredData.reduce((acc, item) => { 23 const key = getGroupKey(item.timestamp, groupBy); 24 if (!acc[key]) acc[key] = []; 25 acc[key].push(item); 26 return acc; 27 }, {}); 28 29 // Calculate metrics for each group ๐Ÿ“ˆ 30 return Object.entries(grouped) 31 .map(([period, items]) => { 32 const dataPoint = { period }; 33 34 selectedMetrics.forEach((metric) => { 35 switch (metric) { 36 case 'revenue': 37 dataPoint[metric] = items.reduce( 38 (sum, item) => sum + item.revenue, 39 0 40 ); 41 break; 42 case 'users': 43 dataPoint[metric] = new Set( 44 items.map((item) => item.userId) 45 ).size; 46 break; 47 case 'conversions': 48 dataPoint[metric] = items.filter((item) => item.converted).length; 49 break; 50 } 51 }); 52 53 return dataPoint; 54 }) 55 .sort((a, b) => new Date(a.period) - new Date(b.period)); 56 }, [rawData, dateRange, groupBy, selectedMetrics]); 57 58 // Chart configuration object ๐Ÿ“Š 59 const chartConfig = useMemo( 60 () => ({ 61 type: chartType, 62 data: processedData, 63 options: { 64 responsive: true, 65 maintainAspectRatio: false, 66 plugins: { 67 title: { 68 display: true, 69 text: `๐Ÿ“Š Analytics Dashboard - ${groupBy.charAt(0).toUpperCase() + groupBy.slice(1)} View`, 70 }, 71 legend: { 72 display: selectedMetrics.length > 1, 73 }, 74 }, 75 scales: { 76 y: { 77 beginAtZero: true, 78 ticks: { 79 callback: function (value) { 80 return selectedMetrics.includes('revenue') 81 ? `๐Ÿ’ฐ $${value}` 82 : value; 83 }, 84 }, 85 }, 86 }, 87 }, 88 }), 89 [chartType, processedData, groupBy, selectedMetrics] 90 ); 91 92 // Event handlers with useCallback ๐ŸŽฏ 93 const handleDateRangeChange = useCallback((newRange) => { 94 setDateRange(newRange); 95 }, []); 96 97 const handleMetricToggle = useCallback((metric) => { 98 setSelectedMetrics((prev) => 99 prev.includes(metric) 100 ? prev.filter((m) => m !== metric) 101 : [...prev, metric] 102 ); 103 }, []); 104 105 const handleExport = useCallback(() => { 106 const csvContent = convertToCSV(processedData); 107 const blob = new Blob([csvContent], { type: 'text/csv' }); 108 const url = URL.createObjectURL(blob); 109 const link = document.createElement('a'); 110 link.href = url; 111 link.download = `๐Ÿ“Š analytics-${groupBy}-${Date.now()}.csv`; 112 link.click(); 113 URL.revokeObjectURL(url); 114 }, [processedData, groupBy]); 115 116 return ( 117 <div className="dashboard"> 118 <div className="controls"> 119 <DateRangePicker value={dateRange} onChange={handleDateRangeChange} /> 120 <GroupBySelector value={groupBy} onChange={setGroupBy} /> 121 <MetricSelector 122 selectedMetrics={selectedMetrics} 123 onToggle={handleMetricToggle} 124 /> 125 <ChartTypeSelector value={chartType} onChange={setChartType} /> 126 </div> 127 128 <div className="chart-container"> 129 <Chart config={chartConfig} /> 130 </div> 131 132 <div className="actions"> 133 <button onClick={handleExport}>๐Ÿ“ฅ Export Data</button> 134 </div> 135 </div> 136 ); 137}; 138 139// Utility function for date grouping ๐Ÿ“… 140const getGroupKey = (timestamp, groupBy) => { 141 const date = new Date(timestamp); 142 switch (groupBy) { 143 case 'day': 144 return date.toISOString().split('T')[0]; 145 case 'week': 146 const weekStart = new Date(date.setDate(date.getDate() - date.getDay())); 147 return weekStart.toISOString().split('T')[0]; 148 case 'month': 149 return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`; 150 default: 151 return timestamp; 152 } 153}; 154 155const convertToCSV = (data) => { 156 if (!data.length) return ''; 157 158 const headers = Object.keys(data[0]); 159 const csvRows = [ 160 headers.join(','), 161 ...data.map((row) => headers.map((header) => row[header]).join(',')), 162 ]; 163 164 return csvRows.join('\n'); 165};

๐Ÿšจ Common Pitfalls and How to Avoid Them

1. โš ๏ธ Overusing the Hooks

jsx
1// โŒ Bad - Unnecessary memoization 2const UserGreeting = ({ user }) => { 3 const greeting = useMemo(() => `๐Ÿ‘‹ Hello, ${user.name}!`, [user.name]); 4 const isLoggedIn = useMemo(() => !!user, [user]); 5 6 return ( 7 <div> 8 {greeting} {isLoggedIn && '๐ŸŽ‰ Welcome back!'} 9 </div> 10 ); 11}; 12 13// โœ… Good - Simple calculations don't need memoization 14const UserGreeting = ({ user }) => { 15 const greeting = `๐Ÿ‘‹ Hello, ${user.name}!`; 16 const isLoggedIn = !!user; 17 18 return ( 19 <div> 20 {greeting} {isLoggedIn && '๐ŸŽ‰ Welcome back!'} 21 </div> 22 ); 23};

2. โš ๏ธ Incorrect Dependencies

jsx
1// โŒ Bad - Missing dependencies 2const SearchResults = ({ query, filters }) => { 3 const results = useMemo(() => { 4 return searchData(query, filters); 5 }, [query]); // Missing 'filters'! ๐Ÿšจ 6 7 return <ResultList results={results} />; 8}; 9 10// โœ… Good - All dependencies included 11const SearchResults = ({ query, filters }) => { 12 const results = useMemo(() => { 13 return searchData(query, filters); 14 }, [query, filters]); // All dependencies included โœ… 15 16 return <ResultList results={results} />; 17};

3. โš ๏ธ Creating New Objects in Dependencies

jsx
1// โŒ Bad - Object created in render 2const ProductList = ({ products }) => { 3 const sortConfig = { field: 'name', direction: 'asc' }; 4 5 const sortedProducts = useMemo(() => { 6 return sortProducts(products, sortConfig); 7 }, [products, sortConfig]); // sortConfig is always new! ๐Ÿšจ 8 9 return <div>{/* render products */}</div>; 10}; 11 12// โœ… Good - Stable dependencies 13const ProductList = ({ products }) => { 14 const sortedProducts = useMemo(() => { 15 const sortConfig = { field: 'name', direction: 'asc' }; 16 return sortProducts(products, sortConfig); 17 }, [products]); // Stable dependencies โœ… 18 19 return <div>{/* render products */}</div>; 20};

๐Ÿ“Š Performance Measurement Strategy

1. ๐Ÿ”ง Use React DevTools Profiler

Before optimizing, always measure performance:

  1. ๐Ÿ”Œ Install React DevTools browser extension
  2. ๐Ÿ“Š Open the Profiler tab
  3. โ–ถ๏ธ Click "Start profiling"
  4. ๐Ÿ–ฑ๏ธ Interact with your app
  5. โน๏ธ Stop profiling and analyze results

2. ๐ŸŽจ Enable Paint Flashing in Chrome DevTools

Paint flashing highlights the areas of the webpage that the browser engine repaints, making it possible for you to visually identify the problematic areas:

How to enable:

  1. ๐ŸŒ Open Chrome DevTools (F12)
  2. Press Command+Shift+P (Mac) or Control+Shift+P (Windows, Linux) to open the Command Menu
  3. Start typing "Rendering" in the Command Menu and select "Show Rendering"
  4. In the Rendering tab, enable "Paint Flashing"
  5. Chrome flashes the screen green whenever repainting happens

What to look for:

  • If you're seeing the whole screen flash green, or areas of the screen that you didn't expect, then you've got some work to do
  • Layout and repaints are expensive in terms of performance and can make your page slow
  • Thanks to Paint flashing loader is marked in green and it's easy to understand which components are repainted

3. ๐Ÿ“ˆ Benchmark Hook

jsx
1const useBenchmark = (name, fn, deps) => { 2 return useMemo(() => { 3 const start = performance.now(); 4 const result = fn(); 5 const end = performance.now(); 6 7 if (end - start > 1) { 8 // Only log if > 1ms โฑ๏ธ 9 console.log(`โšก ${name}: ${(end - start).toFixed(2)}ms`); 10 } 11 12 return result; 13 }, deps); 14}; 15 16// Usage 17const DataProcessor = ({ data }) => { 18 const processedData = useBenchmark( 19 'Data Processing', 20 () => expensiveDataProcessing(data), 21 [data] 22 ); 23 24 return <DataDisplay data={processedData} />; 25};

4. ๐Ÿ” Additional DevTools Features

Scrolling Performance Issues:

  • Open the Rendering tab and check "Scrolling Performance Issues"
  • Chrome gives more details on the scrolling performance issues and highlights the scrolling area
  • It shows a label 'Repaints on scroll' and highlights the scrolling area

Best Practices:

  • ๐ŸŽจ Use Paint Flashing to identify unnecessary repaints
  • ๐Ÿ“Š Combine with React DevTools Profiler for complete performance analysis
  • ๐Ÿ”„ Enable multiple rendering tools simultaneously for comprehensive debugging
  • ๐ŸŽฏ Focus on areas that flash frequently or show red overlays
  • โšก Test on slower devices to catch performance issues early
  • ๐Ÿ“ฑ Use mobile device simulation to test touch scroll performance

๐Ÿ“‹ Best Practices Summary

๐Ÿ’ก When to Use useMemo:

  • ๐Ÿ’ฐ Expensive calculations (complex algorithms, large data processing)
  • ๐Ÿ”— Object/array creation that breaks memoized child components
  • ๐Ÿ”„ Data transformations with multiple dependencies
  • ๐Ÿ“Š Derived state that's expensive to compute

๐ŸŽฏ When to Use useCallback:

  • ๐Ÿ–ฑ๏ธ Event handlers passed to memoized components
  • โš™๏ธ Functions that are dependencies of other hooks
  • ๐ŸŒ API calls that shouldn't be recreated on every render
  • ๐Ÿ”ง Custom hook functions that might be used in multiple places

๐Ÿšซ When NOT to Use Them:

  • ๐Ÿ”ข Simple calculations (string concatenation, basic arithmetic)
  • ๐Ÿ“ฆ Primitive values (strings, numbers, booleans)
  • ๐Ÿ”„ Components that re-render frequently anyway
  • โฐ Premature optimization without performance measurement

๐Ÿ“ Essential Rules:

  1. ๐Ÿ“ Measure first - Use React DevTools Profiler
  2. ๐Ÿ“‹ Include all dependencies - Use ESLint plugin for React hooks
  3. ๐ŸŽฏ Prefer stable dependencies - Avoid creating objects in render
  4. โš™๏ธ Use functional updates - Reduces dependency array size
  5. ๐Ÿค Combine with React.memo - For maximum optimization benefit

โœ… Real-World Optimization Checklist

Before applying these hooks, ask yourself:

  • ๐Ÿ“Š Have I measured the performance issue?
  • ๐Ÿ’ฐ Is this calculation actually expensive?
  • ๐Ÿ”„ Are child components unnecessarily re-rendering?
  • ๐Ÿ—๏ธ Could I restructure components instead?
  • ๐Ÿ“‹ Are all dependencies properly included?
  • โš™๏ธ Am I using functional updates where possible?

๐ŸŽฏ Conclusion

useMemo and useCallback are powerful optimization tools when used correctly. They solve specific problems related to expensive calculations and unnecessary re-renders. The key is understanding when these problems actually exist and applying the right solution.

Remember: React is already highly optimized. These hooks should be used to solve measured performance issues, not applied everywhere preemptively. Start with clean component architecture, measure performance with proper tools, and then optimize the bottlenecks you actually find.

Use the examples and patterns in this guide as templates, but always adapt them to your specific use case and measure the results to ensure you're actually improving performance rather than just adding complexity.

Happy optimizing! ๐Ÿš€โœจ