Introduction
React is a powerful JavaScript library, but as applications grow, performance bottlenecks can arise. Unnecessary re-renders, inefficient state management, and large bundle sizes can lead to slow applications.
This guide covers practical performance optimizations that actually matter. Whether you’re working on a small project or a large-scale application, these best practices will help you write faster, scalable, and efficient React applications.
By the end of this blog, you’ll learn:
✅ Why React apps slow down
✅ Best practices for optimizing re-renders
✅ How to efficiently manage state
✅ How to optimize large lists
✅ lazy loading for faster load times
Let’s dive in! 🚀
1️⃣ Understanding React’s Rendering Behavior
Before optimizing, it’s crucial to understand how React renders components.
📌 React re-renders a component when:
- State changes in that component.
- Props change from the parent component.
- A parent re-renders, causing child components to re-render.
🔍 How to detect unnecessary re-renders?
Use React DevTools Profiler to analyze component renders.
✅ Solution: Memoization & Avoiding Unnecessary Renders
2️⃣ Optimize Component Re-Renders with React.memo
By default, React re-renders a component when its parent renders, even if props haven’t changed.
🚀 Solution: Use React.memo()
to prevent unnecessary renders
🔴 Without React.memo
(Unnecessary re-renders)
import React, { useState } from "react";
const MovieDetails = ({ title, director }) => {
console.log("🎬 MovieDetails component re-rendered");
return (
{title}
Directed by: {director}
);
};
const App = () => {
const [count, setCount] = useState(0);
return (
React.memo Example
setCount(count + 1)}>Click Me ({count})
);
};
export default App;
👉 Here, MovieDetails
re-renders every time the parent renders, even if props hasn't changed.
🟢 Optimizing with React.memo
To prevent unnecessary re-renders, wrap MovieDetails
with React.memo()
:
const MovieDetails = React.memo(({ title, director }) => {
console.log("🎬 MovieDetails component re-rendered");
return (
{title}
Directed by: {director}
);
});
👉 Now, MovieDetails
will only re-render if its props (title or director) change.
Clicking the button will no longer trigger a re-render of MovieDetails
unless new props are passed.
📌 When to use React.memo
?
✅ The component receives static or rarely changing props (e.g., user profile, movie info).
✅ The component is expensive to render (e.g., complex UI elements like charts).
✅ The parent re-renders frequently, but child props remain the same.
3️⃣ Minimize Re-Renders with Derived State
Unnecessary state updates can cause frequent re-renders, slowing down your React app. Instead of storing derived state, compute values when needed.
🔴 Problem: Storing Derived State in useState (Causes Extra Re-Renders)
const [items, setItems] = useState([...]);
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter(item => item.active));
}, [items]); // Extra re-render every time `items` change
📌 Issue:
Every update to items triggers an unnecessary state update for filteredItems
.
This causes an extra re-render that could have been avoided.
🟢 Solution: Use Derived State Instead of Storing It
const filteredItems = items.filter(item => item.active);
✅ Now, filteredItems
is computed only when needed, avoiding extra state updates and re-renders.
4️⃣ Optimize Function Props with useCallback
Passing new function instances as props causes child components to unnecessarily re-render.
🔴 Problem: Without useCallback
(Function re-creates on every render)
const ParentComponent = () => {
const handleClick = () => {
console.log("Button clicked");
};
return ;
};
👉 Even if handleClick
doesn’t change, it gets re-created every time ParentComponent
renders.
🟢 Solution: Use useCallback
to memoize the function
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);
✅ Now, handleClick
retains the same reference, preventing unnecessary re-renders.
5️⃣ Optimize Expensive Computations with useMemo
If a component performs heavy calculations on every render, it can slow down the UI.
🔴 Problem: Without useMemo
(Expensive re-computation)
const heavyComputation = (num) => {
console.log("Expensive calculation running...");
return num * 2;
};
const Component = ({ number }) => {
const result = heavyComputation(number); // Runs on every render
return Result: {result};
};
🟢 Solution: Use useMemo
to cache expensive computations
const result = useMemo(() => heavyComputation(number), [number]);
✅ heavyComputation()
only re-runs when number
changes.
6️⃣ Optimize Lists & Large Data Rendering
🔍 Problem: Rendering large lists slows down UI
Solution: Use Virtualization with react-window
🔴 Without Virtualization (Slow performance)
const List = ({ items }) => {
return (
{items.map((item) => (
{item.name}
))}
);
};
🟢 With Virtualization (react-window)
import { FixedSizeList as List } from "react-window";
const VirtualizedList = ({ items }) => (
{({ index, style }) => (
{items[index].name}
)}
);
✅ Only visible items are rendered, improving performance significantly.
7️⃣ Reduce Bundle Size with Code Splitting & Lazy Loading
Large JavaScript bundles slow down initial page load.
✅ Solution: Use React.lazy()
for dynamic imports
const HeavyComponent = React.lazy(() => import("./HeavyComponent"));
const App = () => (
Loading...