How to stop React components from re-rendering

How to stop React components from re-rendering

ยท

6 min read

by Taofiq Aiyelabegan

React's re-rendering process is necessary to keep the user interface up-to-date with the latest data, but it can also be a performance bottleneck. If components re-render too frequently, it can slow down the application and make it feel unresponsive. This article will show several ways to avoid needless re-rendering for a smoother, faster user interface.

React rendering refers to the process of updating the user interface (UI) of a React application to show the changes in its underlying state or props. When a component's state or props change, React automatically re-renders the component, i.e., it calls the component's render method again to generate a new representation of the user interface.

In this article, we will explain how to use the useMemo, useCallback, and useRef hook to optimize expensive calculations, avoid unnecessary re-renders, and store values that don't need to trigger re-renders. By the end of this article, you'll better understand how to improve your React applications' performance using these powerful React hooks.

Using React.useMemo

The useMemo hook can prevent unnecessary re-renders by memoizing the result of a function and only recomputing it if its dependencies have changed.

Let's take a look at the example below:

import React, { useState, useMemo } from 'react';

function calculateTotal(items) {
  console.log('calculating total');
  return items.reduce((total, item) => total + item.value, 0);
}
function App() {
  const [items, setItems] = useState([
    { id: 1, value: 10 },
    { id: 2, value: 20 },
    { id: 3, value: 30 },
  ]);
  const total = useMemo(() => calculateTotal(items), [items]);
  function handleClick() {
    setItems([
      { id: 1, value: 10 },
      { id: 2, value: 20 },
      { id: 3, value: 40 },
    ]);
  }
  return (
    <div>
      <p>Total: {total}</p>
      <button onClick={handleClick}>Update</button>
    </div>
  );
}

From the above example, we initialize a state named items, representing an array of objects with id and value properties. The calculateTotal function is used to sum up the values of all the items in the array.

The total variable is computed using useMemo, which takes a function that returns the memoized value, and an array of dependencies. In this case, we only pass items as the array. The memoized value is only recalculated if any of the dependencies have changed. In this case, the only dependency is the items array, so calculateTotal will only be called again if the items array has changed.

When the handleClick function is called, it updates the items state to include a new item with a value of 40. Say we did not use the useMemo hook; the entire component would re-render, even though the total value hasn't changed. But because we're using useMemo, calculateTotal is only called once again, resulting in better performance.

useMemo is a useful hook for optimizing performance in React by preventing unnecessary re-renders.

NB: Memoization should only be used for expensive calculations, as it adds some overhead to the rendering process.

React.useRef

The second way to optimize performance in React applications by preventing unnecessary re-renders is using the useRef hook, which can prevent unnecessary re-renders by storing a mutable value that persists across renders.

Below is an example of how useRef can be used to prevent unnecessary re-renders:

import React, { useRef, useState } from 'react';

function App() {
  const [name, setName] = useState('');
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }
  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        ref={inputRef}
      />
      <button onClick={handleClick}>Focus</button>
    </div>
  );
}

Here, we have a state stored as name, which stores the value of an input field. The inputRef variable is created using the useRef hook, which returns a mutable ref object that can be used to store any value.

The inputRef ref object is then passed to the input element as a ref prop. This allows us to directly access the underlying DOM node using inputRef.current.

In the handleClick function, we use inputRef.current.focus() to programmatically focus the input field when the button is clicked.

If we did not use the useRef hook to store the inputRef value, we would have to re-render the entire component every time the handleClick function is called just to get access to the input field. But because we're using useRef, the inputRef value persists across renders, preventing unnecessary re-renders and improving performance.

Also, useRef should only be used for mutable values that don't affect the component's rendering. If a mutable value does affect the rendering of the component, it should be stored in the state instead.


Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay โ€” an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.

OpenReplay

Happy debugging! Try using OpenReplay today.


React.useCallBack

The useCallBack hook memoizes functions so they are not recreated on each component render. When memoized with useCallback, a function will only be created once and reused on subsequent renders if the function's dependencies remain the same.

Let's look at an example of how the useCallBack hook can help prevent unnecessary re-renders.

import React, { useState } from 'react';

function App({ items }) {
  const [visibleItems, setVisibleItems] = useState([]);
  const toggleItemVisibility = (itemId) => {
    if (visibleItems.includes(itemId)) {
      setVisibleItems(visibleItems.filter((id) => id !== itemId));
    } else {
      setVisibleItems([...visibleItems, itemId]);
    }
  };
  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>
          <h2>{item.title}</h2>
          <button onClick={() => toggleItemVisibility(item.id)}>
            {visibleItems.includes(item.id) ? 'Hide' : 'Show'}
          </button>
          {visibleItems.includes(item.id) && <p>{item.content}</p>}
        </div>
      ))}
    </div>
  );
}

Assuming we have the above component that shows the list of items, users can toggle the visibility of each item displayed. The toggleItemVisibility function is created inside the component and passed as a callback to each button's onClick event. However, since toggleItemVisibility depends on the state variable visibleItems, it will be recreated on every render, causing unnecessary component re-renders.

To prevent this, we can make use of the useCallBack hook by memoizing the toggleItemVisibility function like below:

import React, { useState, useCallback } from 'react';

function ItemList({ items }) {
  const [visibleItems, setVisibleItems] = useState([]);
  const toggleItemVisibility = useCallback((itemId) => {
    if (visibleItems.includes(itemId)) {
      setVisibleItems(visibleItems.filter((id) => id !== itemId));
    } else {
      setVisibleItems([...visibleItems, itemId]);
    }
  }, [visibleItems]);
  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>
          <h2>{item.title}</h2>
          <button onClick={() => toggleItemVisibility(item.id)}>
            {visibleItems.includes(item.id) ? 'Hide' : 'Show'}
          </button>
          {visibleItems.includes(item.id) && <p>{item.content}</p>}
        </div>
      ))}
    </div>
  );
}

Here, the visibleItems state is being passed as the second argument to the useCallback hook,ensuring that the toggleItemVisibility function is only recreated when the visibleItems state variable changes. This prevents unnecessary re-renders of the component and improves performance.

Conclusion

In this tutorial, we have learned what React re-rendering is, how it can cause bottlenecks in React applications, and ways of optimizing unnecessary re-renders. Generally, React provides several hooks that can be used to optimize the performance of a React application. These hooks provide a way to store values and functions between renders, which can significantly improve the performance of a React application.