Skip to main content

⚡ Performance Optimization in React – A Complete Guide

React is powerful, but as apps grow, performance can suffer if we’re not careful. Extra re-renders, long lists, or heavy computations can slow down the user experience. Luckily, React provides built-in tools and patterns to keep apps fast and responsive.

In this blog, we’ll explore 7 essential techniques for React performance optimization with definitions, explanations, and practical examples.

1. Reconciliation & Virtual DOM → How React Decides to Re-render
Definition:
React uses a Virtual DOM (a lightweight copy of the real DOM) and a process called Reconciliation to update UI efficiently.
Explanation:
When state/props change, React builds a new Virtual DOM tree.
It compares the new tree with the old one (diffing algorithm).
Only the changed parts are updated in the real DOM.

Example:
function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <h2>Count: {count}</h2> {/* Only this updates */}
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

👉 React doesn’t re-render the entire page, just the changed element.

2. React.memo → Preventing Unnecessary Renders
Definition:
React.memo is a higher-order component that prevents re-rendering if props haven’t changed.
Explanation:
Useful for pure functional components that render the same output given the same props.
Example:
const Child = React.memo(({ value }) => {
  console.log("Child re-rendered");
  return <h3>{value}</h3>;
});

function Parent() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Increase</button>
      <Child value="Static Prop" />
    </div>
  );
}

👉 Without React.memo, Child would re-render every time Parent updates.

3. Key Props → Avoiding List Re-render Issues
Definition:
The key prop helps React identify which items in a list have changed, added, or removed.
Explanation:
If keys are missing or unstable (like using index), React may re-render more than necessary.
Always use unique IDs when possible.

Example:
const items = ["A", "B", "C"];

function List() {
  return (
    <ul>
      {items.map(item => <li key={item}>{item}</li>)}
    </ul>
  );
}

👉 Correct keys help React efficiently update only changed list items.

4. Lazy Loading Components → React.lazy + Suspense
Definition:
Lazy loading means loading components only when needed, reducing the initial bundle size.
Explanation:
React provides React.lazy for dynamic imports, and Suspense for fallback UI.
Example:
import React, { lazy, Suspense } from "react";

const Dashboard = lazy(() => import("./Dashboard"));

function App() {
  return (
    <div>
      <h1>Welcome</h1>
      <Suspense fallback={<p>Loading dashboard...</p>}>
        <Dashboard />
      </Suspense>
    </div>
  );
}

👉 Dashboard is loaded only when rendered, not at initial load.

5. Windowing / Virtualization → Handling Large Lists
Definition:
Windowing (or virtualization) means rendering only visible items in a list, not all items at once.
Explanation:
Libraries like react-window and react-virtualized render only what’s needed, making huge lists fast.
Example with react-window:

import { FixedSizeList as List } from "react-window";

function App() {
  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);
  
  return (
    <List height={300} itemCount={items.length} itemSize={35} width={300}>
      {({ index, style }) => <div style={style}>{items[index]}</div>}
    </List>
  );
}

👉 Even with 10,000 items, only a handful are rendered at once.

6. Profiling → Measuring Performance
Definition:
React DevTools Profiler helps you measure which components render, why, and how long they take.
Explanation:
You can record interactions and view flame graphs.
Helps identify unnecessary re-renders and bottlenecks.

Steps to use:

1. Install React DevTools (Chrome/Firefox extension).
2. Go to the Profiler tab.
3. Record interactions and analyze flame graphs.


👉 Profiling ensures you optimize where it matters, not everywhere.

7. Batching Updates → Automatic Batching in React 18

Definition:
Batching means combining multiple state updates into a single re-render for efficiency.
Explanation:
Before React 18, batching only happened inside event handlers. Now, it also works in promises, setTimeouts, etc.
Example:

function App() {
  const [a, setA] = React.useState(0);
  const [b, setB] = React.useState(0);

  function update() {
    setTimeout(() => {
      setA(1);
      setB(1); // Both updates batched in React 18
    }, 1000);
  }

  return (
    <div>
      <button onClick={update}>Update</button>
      <p>A: {a}, B: {b}</p>
    </div>
  );
}

👉 React 18 batches these updates automatically, improving performance.


🎯 Final Thoughts

Performance optimization in React isn’t about blindly adding React.memo everywhere. It’s about understanding when and where to optimize.

Reconciliation & Virtual DOM → React updates only what’s necessary.
React.memo → Prevents unnecessary re-renders.
Keys → Ensure efficient list rendering.
Lazy Loading → Load components only when needed.
Windowing → Handle massive lists efficiently.
Profiling → Find actual bottlenecks.
Batching → React 18 improves updates automatically.

🚀 With these techniques, your React apps will stay fast, scalable, and smooth.

Comments

Popular posts from this blog

🌟 Dot net Microservices interview questions

Here is a comprehensive list of 200 .NET microservices coding questions covering all core microservices concepts and cross-cutting concerns relevant for designing, building, deploying, and maintaining .NET-based distributed systems. 🧩 A. Microservices Fundamentals (20) Build a microservice in .NET 8 that exposes a simple CRUD API. Implement communication between two microservices using REST. How would you design microservices for an e-commerce application? Create a microservice that handles user registration and login. How do you isolate domain logic in a microservice? How to apply the "Single Responsibility Principle" in microservices? Design a service registry/discovery mechanism using custom middleware. Implement a service that handles file uploads and metadata separately. Build a stateless microservice and explain its benefits. Implement health check endpoints in .NET 8. Demonstrate versioning in a microservice API. Add Swagger/OpenAPI support to your m...

⚡ Part 1: Introduction to Generics in C#

🌍 Why Do We Need Generics? Imagine you want to create a stack (like a pile of books 📚): You can push items on top You can pop items off the top If we write a stack for integers : public class IntStack { private int[] items = new int[10]; private int index = 0; public void Push(int item) => items[index++] = item; public int Pop() => items[--index]; } 👉 Problem: This only works for int . What if we want a string stack ? Or a Customer stack ? We’d have to duplicate code for every type. 😢 ✅ Solution: Generics Generics let us create type-safe reusable code without duplication. We can say: “I don’t care what type it is yet — I’ll decide later.” 1) Generic Classes Here’s a generic stack : // Generic class "Stack<T>" // The <T> is a placeholder for any type public class Stack<T> { private T[] items = new T[10]; // Array of type T private int index = 0; // Push adds an item of type T public void P...

🚪 Part 9: API Gateway for .NET 8 Microservices (Ocelot & YARP)

Once you have multiple microservices (Products, Orders, Payments…), exposing each one directly to clients gets messy: Different base URLs Duplicated auth logic No unified rate limiting / caching Hard to evolve routes or aggregate data 👉 Enter the API Gateway — your single front door for all microservices. An API Gateway handles: ✅ Routing & path rewriting ✅ Load balancing, retries, circuit breakers ✅ Authentication & Authorization (JWT, OAuth2) ✅ Rate limiting & caching ✅ Aggregation (compose results from multiple services) In this post we’ll implement two strong options: Ocelot → config-driven, mature, DevOps-friendly YARP (Yet Another Reverse Proxy) → Microsoft’s code-first, extensible gateway ⚖️ Ocelot vs YARP — When to Choose Ocelot → JSON config, minimal C#, built-in QoS (rate limit, circuit breaker). Perfect for teams that like DevOps config-as-code. YARP → full C# control, middleware-friendly, can embed into broader apps (e.g. add dashb...