Introduction
After writing React code for over 4 years, one recurring challenge I’ve faced is optimizing performance. React apps can easily accumulate technical debt if performance considerations are overlooked from the start.
While it’s not always practical to optimize everything upfront, regularly scheduling performance reviews and applying key techniques helps avoid major issues later.
In this article, I’ll cover some of the most effective React performance optimization techniques you can integrate into your workflow immediately.
1. Optimizing Large Lists with Virtualization
Rendering long lists can cause slow performance due to unnecessary DOM nodes. Virtualization ensures only visible items are rendered.
Popular libraries for list virtualization:
- React Window
- React Virtualized
Example Using React Window:
import { FixedSizeList as List } from 'react-window';
const MyList = ({ items }) => (
<List height={500} itemCount={items.length} itemSize={35} width={300}>
{({ index, style }) => (
<div style={style}>{items[index]}</div>
)}
</List>
);
2. Memoization Using useMemo
useMemo
helps cache the result of expensive calculations until dependencies change. This prevents unnecessary recalculations on every render.
Syntax:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Example:
import React, { useState, useMemo } from 'react';
const ExpensiveComponent = ({ a, b }) => {
const computeExpensiveValue = (a, b) => a + b;
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return <p>Computed Value: {memoizedValue}</p>;
};
3. Code Splitting
Instead of bundling everything into a single file, code splitting allows parts of your app to load only when needed.
Example:
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState(null);
const loadComponent = async () => {
const { default: LoadedComponent } = await import('./MyComponent');
setComponent(<LoadedComponent />);
};
return (
<div>
<button onClick={loadComponent}>Load Component</button>
{component}
</div>
);
}
export default App;
4. React Lazy Loading with Suspense
React.lazy
dynamically imports components only when required.
Example:
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./MyComponent'));
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
export default App;
5. Throttling and Debouncing
Both techniques limit how often a function is executed, especially useful for event-heavy actions like scrolling, resizing, or typing.
Throttling Example:
const throttle = (func, delay) => {
let lastCall = 0;
return () => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func();
}
};
};
Debouncing Example:
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
Connect With Me
Let's connect:
I’m also open to freelance writing collaborations.
Conclusion
Performance optimization is essential for scaling React applications. By incorporating virtualization, memoization, code splitting, lazy loading, throttling, and debouncing, your apps can remain fast and responsive as they grow.
Thank you for reading! If you found this article useful, feel free to share it.