Hello, I'm

Vijay Sharma

I'm a Full Stack Web Developer

An enthusiastic person currently shaping the future of software development by designing and developing smooth user interfaces that promote user interaction with information and data.

About Me

Optimizing Performance in React : Reducing Unnecessary Re-renders

Introduction

In React applications, performance optimization is a key concern, especially when dealing with complex UIs. Unnecessary re-renders can slow down the application and affect user experience. This article explores how to reduce redundant re-renders in React using tools like React.memo and useCallback.

Understanding the Problem: Unnecessary Re-renders

React's reconciliation algorithm ensures that the DOM updates efficiently. However, React re-renders components whenever their state or props change—even if the updates don't affect a specific component. This can lead to inefficiencies, especially when multiple components rely on a shared state.

Example Scenario: Optimizing a Parent-Child State Sharing

Below is an example of a React application where a parent component manages the state for multiple child components. Each child represents an item with a text input field, and any change to the input updates the parent state.

The Child Component 👇🏻
import { memo } from "react";

const Item = memo(({ id, value, onChange }) => {
    debugger
    return (
        <div>
            <h5>RE-RENDER {(Math.random() * 100).toFixed()}</h5>
            <label>Item</label>
            <input
                type="text"
                name="text"
                id="text"
                value={value}
                onChange={e => onChange(id, e.target.value)}
            />
        </div>
    )
})

export default Item;
Here: 👆🏻

→ The Item component is memoized using React.memo to prevent unnecessary re-renders.
→ BetterThe RE-RENDER marker, with Math.random(), helps visualize when re-renders occur.

The Parent Component 👇🏻
import React, { useCallback, useState } from "react";
import Item from "./Item";

export default function OptimizePerformaceReRendering() {

    const [itemValues, setItemsValues] = useState([
        { value: '', id: 1 },
        { value: '', id: 2 },
        { value: '', id: 3 },
    ])

    const changeValue = useCallback((id, value) => {
        debugger;
        setItemsValues(prevItem => prevItem.map(item => {
            if (item.id === id) {
                item.value = value;
            }
            return item;
        }))
    }, [])

    return (
        <div>
            <header>
                <h1>Parent holds the state and passes it to items</h1>
                <p className="app-header-output">
                    {JSON.stringify(itemValues)}
                </p>
            </header>

            <form>
                <div style={{ display: "flex", justifyContent: "space-evenly" }}>
                    {
                        itemValues.map(itemValue => {
                            return (
                                <Item
                                    key={itemValue.id}
                                    id={itemValue.id}
                                    value={itemValue.value}
                                    onChange={changeValue}
                                />
                            )
                        })
                    }
                </div>
            </form>
        </div>
    )
}
Here:👆🏻

→ The itemValues state is managed in the parent.
→ The changeValue function updates the state immutably using useCallback to memoize the function.
→ The Item component receives props for id, value, and an onChange handler.

Key Optimizations Explained

1. Using React.memo:

The React.memo higher-order component prevents re-renders of functional components unless their props have changed. In the Item component, this ensures that only the input field whose value changes is re-rendered.


2. Using useCallback:

The useCallback hook ensures that the changeValue function is not re-created on every render. Without useCallback, React would pass a new function instance to each Item, triggering unnecessary renders.


3. Updating State Immutably :

In the changeValue function, we use the spread operator ({ ...item, value }) to create a new object for the updated item. This ensures React can detect state changes and trigger updates efficiently.

Observing the Optimized Behavior

When you run the application:

→ Initially, all child components render once.
→ As you type in an input field, only the corresponding Item component re-renders, as indicated by the RE-RENDER value changing.


Without React.memo and useCallback, every child component would re-render upon any input change.

Performance Gains in Real Applications

While this example has only three child components, the benefits of these optimizations are more apparent in larger applications with many child components or complex state updates. Reducing unnecessary renders can significantly improve UI responsiveness and reduce memory consumption.

Conclusion :
Performance optimization in React often revolves around preventing unnecessary work. By using React.memo and useCallback, along with immutable state updates, you can ensure that only the components affected by state changes re-render
Remember :

→ Use React.memo to memoize components.
→ Use useCallback to memoize functions passed as props.
→ Update state immutably to ensure React detects changes.


These techniques are essential for building efficient, scalable React applications. Explore the example code, try tweaking it, and see the impact of these optimizations firsthand!

Post a Comment

0 Comments