Lesson 57 of 60 – React Performance Optimization
95%

React Performance Optimization

Performance optimization means improving a React application's speed, responsiveness, and efficiency. React already provides an efficient rendering system, but large applications may need additional techniques to reduce unnecessary work.

Note: Do not optimize every component unnecessarily. First identify an actual performance problem, then apply an optimization that addresses it.

1. What is React Performance?

React performance describes how efficiently a React application renders, updates, and responds to user interactions.

Good performance can result in:

  • Faster interactions
  • Smoother user experience
  • Less unnecessary rendering
  • Better responsiveness
  • Efficient use of browser resources

2. What Causes Performance Problems?

Performance problems can come from different parts of an application.

  • Unnecessary component renders
  • Expensive calculations
  • Large lists
  • Large API responses
  • Too many network requests
  • Unnecessary effects
  • Large images and assets
  • Complex component trees

3. React Re-rendering

A component may render again when its state or relevant inputs change. Parent component updates can also cause child components to render again.

function Counter() {

    const [count, setCount] =
        useState(0);

    return (
        <button
            onClick={() =>
                setCount(count + 1)
            }
        >

            {count}

        </button>
    );
}

4. Avoid Unnecessary Work

One important performance principle is to avoid doing expensive work when it is not needed.

const result =
    expensiveCalculation(data);

If the calculation is expensive and the required inputs rarely change, memoization may sometimes be useful.

5. React.memo

React.memo can memoize a component so React can skip rendering it when its props have not changed according to the memoization comparison.

const UserCard =
    React.memo(function UserCard({
        name
    }) {

        return (
            <div>
                {name}
            </div>
        );
    });

6. When React.memo Can Help

React.memo can be useful when:

  • A component renders frequently.
  • The component is relatively expensive to render.
  • Its props often remain unchanged.
  • The parent renders frequently for unrelated reasons.

It is not automatically beneficial for every component.

7. useMemo

useMemo can cache the result of a calculation between renders until its dependencies change.

const filteredUsers =
    useMemo(() => {

        return users.filter(
            user =>
                user.active
        );

    }, [users]);

8. Expensive Calculations

An expensive calculation may involve processing a large amount of data.

function calculateTotal(items) {

    return items.reduce(
        (total, item) =>
            total + item.price,
        0
    );
}

If the calculation is expensive and does not need to run on every render, memoization can be considered.

9. useCallback

useCallback can preserve a function reference between renders until its dependencies change.

const handleClick =
    useCallback(() => {

        console.log("Clicked");

    }, []);

This can be useful when passing callbacks to memoized child components.

10. useMemo vs useCallback

Hook Purpose
useMemo Memoizes a calculated value
useCallback Memoizes a function reference
useMemo(
    () => calculateValue(),
    [data]
);

useCallback(
    () => handleAction(),
    [data]
);

11. Avoid Unnecessary Effects

An unnecessary useEffect can make code more complicated and cause additional updates.

useEffect(() => {

    setFullName(
        firstName + " " + lastName
    );

}, [firstName, lastName]);

If fullName is simply derived from existing state or props, it may often be calculated directly during rendering instead.

12. Derived Data

Derived data can often be calculated from existing state instead of storing duplicate state.

const fullName =
    firstName + " " + lastName;

Keeping only the necessary state can make the component easier to understand and reduce synchronization work.

13. Efficient List Rendering

Large lists can require significant rendering work.

{users.map(user => (

    <UserCard
        key={user.id}
        user={user}
    />

))}

Use stable keys and consider additional techniques when displaying very large lists.

14. List Virtualization

For extremely large lists, rendering every item at once can be expensive. Virtualization renders only the items currently needed for the visible area.

Large List
    ↓
Visible Items
    ↓
Render Only Needed Items

Libraries designed for list virtualization can be used when an application has a suitable large-list requirement.

15. Pagination

Pagination can reduce the amount of data displayed at one time.

Page 1
Page 2
Page 3
Page 4

Instead of rendering thousands of records at once, the application can display a smaller group of records.

16. Lazy Loading

Lazy loading allows parts of an application to be loaded when they are needed instead of loading everything immediately.

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

Lazy loading is commonly used with route-level code splitting.

17. React.lazy()

React.lazy() can dynamically load a component.

import {
    lazy,
    Suspense
} from "react";

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

function App() {

    return (
        <Suspense
            fallback={
                <p>
                    Loading...
                </p>
            }
        >

            <About />

        </Suspense>
    );
}

18. Code Splitting

Code splitting divides application JavaScript into smaller chunks that can be loaded when required.

Application
      ↓
 ┌────┼────┐
 ↓    ↓    ↓
Home About Dashboard

This can reduce the amount of JavaScript required for the initial page load.

19. Optimizing Images

Large images can increase page loading time.

Useful practices include:

  • Use appropriately sized images.
  • Compress large images.
  • Use modern image formats when appropriate.
  • Lazy-load images when suitable.
  • Avoid unnecessarily large image dimensions.
<img
    src="/images/course.webp"
    alt="React Course"
    loading="lazy"
/>

20. Optimizing API Requests

Unnecessary API requests can slow down an application.

Consider:

  • Request only required data.
  • Avoid duplicate requests.
  • Use appropriate caching strategies.
  • Handle loading and error states.
  • Use pagination for large datasets.
fetch("/api/products?page=1")
    .then(response =>
        response.json()
    )
    .then(data =>
        setProducts(data)
    );

21. Debouncing Search

When a user types into a search box, sending an API request for every keystroke may create unnecessary requests.

User types:
R
Re
Rea
Reac
React

Instead of requesting every time,
wait briefly before sending the request.

This technique is commonly called debouncing.

22. Avoid Creating Unnecessary Objects

Creating new objects or functions during every render can affect referential equality and may matter when working with memoized components.

const options = {
    sort: "name"
};

return (
    <Child
        options={options}
    />
);

If object identity causes unnecessary child renders, the structure can be reviewed and, where appropriate, memoization can be considered.

23. Component Splitting

Breaking a large component into smaller components can improve code organization and make performance behavior easier to reason about.

Dashboard
 ├── Header
 ├── Sidebar
 ├── Statistics
 ├── UserList
 └── Footer

Component splitting should be based on meaningful responsibilities rather than creating unnecessary tiny components.

24. React DevTools Profiler

The React Developer Tools Profiler can help identify components that render and how long rendering takes.

Performance Problem
        ↓
Profile Application
        ↓
Find Expensive Render
        ↓
Identify Cause
        ↓
Apply Optimization
        ↓
Measure Again

Profiling helps prevent guessing about performance problems.

25. Avoid Premature Optimization

Premature optimization means adding optimization techniques before there is evidence that they are needed.

For example, adding useMemo to every calculation may make code more complicated without providing a meaningful benefit.

Remember: Measure first, identify the bottleneck, optimize the relevant part, and measure again.

26. Common Performance Mistakes

  • Using memoization everywhere without measuring.
  • Rendering unnecessarily large lists.
  • Making duplicate API requests.
  • Using unnecessary effects.
  • Loading large images without optimization.
  • Loading every application feature immediately.
  • Keeping duplicated state.
  • Ignoring browser and React profiling tools.
  • Creating unnecessary complex component trees.

27. Performance Optimization Workflow

Build Application
       ↓
Measure Performance
       ↓
Find Bottleneck
       ↓
Understand Cause
       ↓
Apply Suitable Optimization
       ↓
Measure Again
       ↓
Keep or Remove Optimization

28. Complete Memoized List Example

import {
    useMemo,
    useState,
    memo
} from "react";

const UserCard =
    memo(function UserCard({
        user
    }) {

        return (
            <div className="p-3 border mb-2">

                <strong>
                    {user.name}
                </strong>

            </div>
        );
    });

function Users() {

    const [users] = useState([
        {
            id: 1,
            name: "Amit"
        },
        {
            id: 2,
            name: "Ravi"
        },
        {
            id: 3,
            name: "Neha"
        }
    ]);

    const [search, setSearch] =
        useState("");

    const filteredUsers =
        useMemo(() => {

            return users.filter(
                user =>
                    user.name
                        .toLowerCase()
                        .includes(
                            search.toLowerCase()
                        )
            );

        }, [users, search]);

    return (
        <div>

            <input
                value={search}
                onChange={e =>
                    setSearch(
                        e.target.value
                    )
                }
                placeholder="Search users"
            />

            {filteredUsers.map(
                user => (

                <UserCard
                    key={user.id}
                    user={user}
                />

            ))}

        </div>
    );
}

export default Users;

29. Performance Optimization Checklist

  • Measure before optimizing.
  • Check unnecessary renders.
  • Review expensive calculations.
  • Use React.memo where it provides a meaningful benefit.
  • Use useMemo for appropriate expensive calculations.
  • Use useCallback when stable function references are useful.
  • Optimize large lists.
  • Use lazy loading and code splitting where appropriate.
  • Optimize images and network requests.
  • Use profiling tools to verify improvements.

30. React Performance Best Practices

  • Keep component state as local as practical.
  • Avoid unnecessary state and duplicated derived data.
  • Keep effects focused on actual side effects.
  • Use stable keys when rendering lists.
  • Optimize large lists with suitable techniques.
  • Load large application features only when needed.
  • Optimize images and API requests.
  • Use memoization only when it solves a real problem.
  • Profile before and after important optimizations.
  • Prefer simple and maintainable code over unnecessary optimization.
Measure
   ↓
Find Problem
   ↓
Optimize
   ↓
Measure Again

📌 Key Points

  • React performance is about efficient rendering and responsive user interactions.
  • Unnecessary renders and expensive calculations can affect performance.
  • React.memo can skip some unnecessary component renders.
  • useMemo memoizes a calculated value.
  • useCallback memoizes a function reference.
  • Large lists may benefit from pagination or virtualization.
  • Lazy loading and code splitting can reduce initial application work.
  • Images and API requests should be optimized.
  • React Developer Tools can help identify rendering problems.
  • Always measure performance before and after applying an optimization.

🧠 Quick Quiz

Question: Which React hook is used to memoize the result of a calculation?