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:

  1. State changes in that component.
  2. Props change from the parent component.
  3. 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...
}> );
Enter fullscreen mode Exit fullscreen mode