
React Performance Optimization Guide: 10 Proven Techniques to Speed Up Your Apps
<hr>
<h2 id="understanding-react-performance">1. Understanding React Performance</h2>
<h3>How React Renders</h3>
<p>React's rendering process involves three main phases:</p>
<ol>
<li><strong>Trigger</strong>: State or props change</li>
<li><strong>Render</strong>: React calculates what changed (runs component function)</li>
<li><strong>Commit</strong>: React updates the DOM</li>
</ol>
<div class="example-block">
<pre><code>// Understanding render behavior
function Parent() { const [count, setCount] = useState(0); console.log('Parent renders'); // Logs on every state change return ( <div> <button onClick={() => setCount(count + 1)}> Count: {count} </button> {/* Child also re-renders! */} <Child /> </div> ); } function Child() { console.log('Child renders'); // Logs even though it has no props! return <div>I'm a child component</div>; }</code></pre> <p><strong>Key Point</strong>: When a parent component re-renders, all child components re-render by default, even if their props haven't changed. This is the root of most unnecessary re-renders.</p> </div>
<hr>
<h2 id="how-to-measure-performance">2. How to Measure Performance</h2>
<h3>Using React DevTools Profiler</h3>
<pre><code>// Wrap components you want to profile
import { Profiler } from 'react';
function App() {
const onRenderCallback = (
id, // component name
phase, // "mount" or "update"
actualDuration, // time spent rendering
baseDuration, // estimated time without memoization
startTime,
commitTime
) => {
console.log(${id} took ${actualDuration}ms to render);
};
return ( <Profiler id="Dashboard" onRender={onRenderCallback}> <Dashboard /> </Profiler> ); }</code></pre>
<h3>Chrome DevTools Performance Tab</h3>
<ol>
<li>Open Chrome DevTools (F12).</li>
<li>Go to the **Performance** tab.</li>
<li>Click **Record**, interact with your app, and then stop recording to analyze CPU usage and rendering activity.</li>
</ol>
<h3>Lighthouse Audit</h3>
<p>Use Lighthouse for a comprehensive report on speed, accessibility, and SEO metrics.</p>
<pre><code># Install Lighthouse CLI
npm install -g lighthouse
Run audit
lighthouse https://your-app.com --view</code></pre>
<hr>
<h2 id="reactmemo-for-component-memoization">3. React.memo() for Component Memoization</h2>
<p><code>React.memo()</code> is a Higher-Order Component (HOC) that prevents functional components from re-rendering if their **props are shallowly equal** to the previous props.</p>
<h3>The Solution</h3>
<pre><code>// ā
Memoized component only re-renders when props change
import { memo } from 'react';
const ExpensiveChild = memo(function ExpensiveChild({ count }) { console.log('ExpensiveChild rendered'); return <div>Count: {count}</div>; });
// ā Custom comparison function (Use sparingly) const UserCard = memo( function UserCard({ user }) { return <div>{user.name}</div>; }, (prevProps, nextProps) => { // Return true if props are equal (skip render) return prevProps.user.id === nextProps.user.id; } );</code></pre>
<div class="tip-box">
<strong>When to Use React.memo()</strong>
<p>ā
**Use when:** Component is computationally expensive OR renders often with the same props (e.g., items in a long list).</p>
<p>ā **Don't use when:** Component rarely re-renders, is very simple, or its props change frequently.</p>
</div>
<hr>
<h2 id="usememo-hook-for-expensive-calculations">4. useMemo Hook for Expensive Calculations</h2>
<p><code>useMemo()</code> memoizes a **value**. It recomputes the value only when one of its dependencies changes, preventing expensive calculations from running on every render.</p>
<h3>The Solution</h3>
<pre><code>// ā
Calculation only runs when dependencies change
import { useMemo } from 'react';
function ProductList({ products, searchTerm }) { const filteredProducts = useMemo(() => { console.log('Filtering products...'); return products .filter(p => p.name.includes(searchTerm)) .sort((a, b) => b.rating - a.rating) .slice(0, 20); }, [products, searchTerm]); // <-- Dependencies
return ( <ul> {filteredProducts.map(p => (<li key={p.id}>{p.name}</li>))} </ul> ); }</code></pre>
<hr>
<h2 id="usecallback-hook-for-function-references">5. useCallback Hook for Function References</h2>
<p><code>useCallback()</code> memoizes a **function instance**. It returns the exact same function reference unless one of its dependencies changes.</p>
<h3>The Problem</h3>
<p>Passing a new function (created on every parent render) as a prop to a memoized child component (using `React.memo()`) causes the child to re-render, defeating the purpose of memoization.</p>
<h3>The Solution</h3>
<pre><code>// ā
Stable function reference
import { useCallback } from 'react';
function SearchBar({ onSearch }) { const [query, setQuery] = useState('');
const handleSearch = useCallback(() => { onSearch(query); }, [query, onSearch]); // Only creates new function when dependencies change
return ( <div> <input onChange={e => setQuery(e.target.value)} /> {/* MemoizedButton will not unnecessarily re-render now */} <MemoizedButton onClick={handleSearch} /> </div> ); }</code></pre>
<hr>
<h2 id="code-splitting-and-lazy-loading">6. Code Splitting and Lazy Loading</h2>
<p>Code splitting divides your bundle into smaller chunks that are loaded on demand. This significantly improves the initial load time (Time To Interactive).</p>
<h3>The Solution: Route-Based Code Splitting</h3>
<pre><code>// ā
Load components only when needed
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard')); const Settings = lazy(() => import('./Settings'));
function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Router> <Route path="/" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Router> </Suspense> ); }</code></pre>
<h3>Component-Level Code Splitting</h3>
<p>You can also lazily load heavy components like charts or rich-text editors within a view, only if they are about to be displayed.</p>
<hr>
<h2 id="virtualization-for-long-lists">7. Virtualization for Long Lists</h2>
<p>Virtualization (or windowing) renders **only the items currently visible** in the viewport, reusing the DOM elements as the user scrolls. This is essential for lists with thousands of items.</p>
<h3>The Solution: React Virtual (<code>react-window</code>)</h3>
<pre><code>// ā
Only renders visible items
import { FixedSizeList } from 'react-window';
function ProductGrid({ products }) { const Row = ({ index, style }) => ( <div style={style}>{products[index].name}</div> );
return ( <FixedSizeList height={500} width={600} itemCount={products.length} itemSize={50} // Fixed height > {Row} </FixedSizeList> ); }</code></pre>
<hr>
<h2 id="optimizing-images-and-assets">8. Optimizing Images and Assets</h2>
<p>Images are often the largest payload. Optimizing them is crucial for Core Web Vitals (LCP).</p>
<ul>
<li>**Lazy Loading:** Use the `loading="lazy"` attribute on `<img>` tags.</li>
<li>**Responsive Images:** Use the `<picture>` element and `srcset` to serve different image sizes based on device screen size.</li>
<li>**Modern Formats:** Convert images to modern formats like **WebP** for better compression and smaller file sizes.</li>
</ul>
<hr>
<h2 id="debouncing-and-throttling">9. Debouncing and Throttling</h2>
<p>These techniques limit how often an expensive function is called, especially in response to rapid events like scrolling, typing, or resizing.</p>
<h3>Debouncing Search Input</h3>
<p>Debouncing ensures an expensive function (like an API call) is only executed **after a specific time delay** since the last event occurred (e.g., 500ms after the user stops typing).</p>
<pre><code>// Example: Custom useDebounce hook
// The API call (fetchResults) only happens after the user stops typing for 500ms. useEffect(() => { if (debouncedQuery) { fetchResults(debouncedQuery); } }, [debouncedQuery]);</code></pre>
<h3>Throttling Scroll Events</h3>
<p>Throttling ensures a function (like an infinite scroll handler) is executed at most **once every specific time interval** (e.g., every 200ms), preventing hundreds of function calls during fast scrolling.</p>
<hr>
<h2 id="production-build-optimization">10. Production Build Optimization</h2>
<p>Ensure your final build is as small and efficient as possible.</p>
<ul>
<li>**Webpack Bundle Analysis:** Use tools like `webpack-bundle-analyzer` to visualize dependencies and identify oversized modules.</li>
<li>**Tree Shaking:** Webpack automatically removes "dead code" (unused exports). Help it by using specific imports (e.g., `import debounce from 'lodash/debounce'`) instead of importing the entire library.</li>
<li>**Environment Variables:** Use `process.env.NODE_ENV === 'production'` to strip out development code (like Proptypes validation) in the final build.</li>
</ul>
<hr>
<h2 id="web-vitals">11. Web Vitals and Performance Metrics</h2>
<p>Core Web Vitals are a set of metrics Google uses to quantify user experience. You should monitor them continuously.</p>
<ul>
<li>**LCP (Largest Contentful Paint):** Measures loading performance (aim for < 2.5s).</li>
<li>**FID (First Input Delay):** Measures interactivity (aim for < 100ms).</li>
<li>**CLS (Cumulative Layout Shift):** Measures visual stability (aim for < 0.1).</li>
</ul>
<p>Use the `web-vitals` library to measure and report these metrics to your analytics service.</p>
<hr>
<h2 id="mistakes-to-avoid">12. Common Performance Mistakes</h2>
<div class="warning-box">
<p><span class="mistake">ā Mistake #1: Anonymous Functions in JSX</span></p>
<p>Creates a new function on every render, breaking memoization in child components. **Fix:** Use `useCallback()` or define the function outside the render.</p>
</div>
<div class="warning-box">
<p><span class="mistake">ā Mistake #2: Inline Objects/Arrays as Props</span></p>
<p>Because `{} !== {}` and `[] !== []`, passing inline objects or arrays (even if their contents are the same) as props causes memoized children to re-render. **Fix:** Use `useMemo()` to memoize the object or array.</p>
</div>
<div class="warning-box">
<p><span class="mistake">ā Mistake #3: Not Using Keys Properly</span></p>
<p>Using the array index as a key (<code>index</code>) can lead to DOM instability and performance issues when list items are reordered, added, or removed. **Fix:** Always use a **unique, stable ID** for keys.</p>
</div>
<hr>
<h2 id="real-world-example">Real-World Optimization Example</h2>
<p>The code below shows a typical component **Before** and **After** applying memoization and dependency optimization.</p>
<h3>After Optimization (Key Changes)</h3>
<pre><code>import { memo, useMemo, useCallback, useEffect } from 'react';
// 1. ā Memoize Child Component const ItemCard = memo(function ItemCard({ item, onClick }) { return <li onClick={onClick}>{item.name}</li>; });
function Dashboard({ userId }) {
// 2. ā
Depend on userId
useEffect(() => {
fetch(/api/users/${userId}/dashboard).then(setData);
}, [userId]);
// 3. ā
Memoized calculation
const stats = useMemo(() => {
if (!data) return { total: 0, count: 0 };
return data.items
.filter(i => i.status === 'active')
.reduce((acc, i) => { acc.total += i.value; acc.count++; return acc; }, { total: 0, count: 0 });
}, [data]);
// 4. ā
Memoized callback
const handleItemClick = useCallback((id) => {
updateItem(id);
}, []);
return (
<ul>
{/* 5. ā
Stable Key & Memoized Props */}
{data?.items.map((item) => (
<ItemCard
key={item.id}
item={item}
onClick={() => handleItemClick(item.id)}
/>
))}
</ul>
);
}</code></pre>
<hr>
<h2 id="checklist">Performance Optimization Checklist</h2>
<div class="checklist-container">
<h3>Development Phase</h3>
<ul>
<li>Use React DevTools Profiler to find bottlenecks.</li>
<li>Implement <code>React.memo()</code> for expensive components.</li>
<li>Use <code>useMemo()</code> for expensive calculations.</li>
<li>Use <code>useCallback()</code> for event handlers passed to children.</li>
</ul>
<h3>Build & Deployment Phase</h3>
<ul>
<li>Implement code splitting with <code>React.lazy()</code>.</li>
<li>Optimize all images (WebP, lazy loading, responsive).</li>
<li>Implement virtualization for long lists.</li>
<li>Tree-shake unused dependencies to keep bundle size small.</li>
<li>Monitor Core Web Vitals (LCP, FID, CLS) in production.</li>
</ul>
</div>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>React performance optimization is an ongoing process. Focus on:</p>
<ol>
<li>**Measure first** - Use profiling tools to identify bottlenecks.</li>
<li>**Optimize strategically** - Focus on the biggest wins (unnecessary re-renders, large bundles).</li>
<li>**Monitor continuously** - Track performance metrics in production.</li>
</ol>
<div class="tip-box">
<strong>Key Takeaways</strong>
<ul>
<li>ā
Use <code>React.memo()</code>, <code>useMemo()</code>, and <code>useCallback()</code> wisely.</li>
<li>ā
Implement code splitting and lazy loading.</li>
<li>ā
Virtualize long lists and optimize assets.</li>
<li>ā
Monitor Core Web Vitals.</li>
<li>Remember: Premature optimization is the root of all evil. Optimize when you have a measured problem.</li>
</ul>
</div>
</div>
</body>
</html>