React Performance

React is a powerful JavaScript library for building complex user interfaces. However, as applications grow in complexity, it's important to ensure that the performance of the application remains fast and responsive. In this chapter, we'll explore some tips and techniques for optimizing React performance.

Why components re-renders

React re-renders components for a variety of reasons, including:

  1. Change in Props: If the props of a component change, React will re-render the component to reflect the new values.

  2. Change in State: If the state of a component changes, React will re-render the component to reflect the new state.

  3. Parent Component Re-renders: If a parent component re-renders, React may re-render its child components as well.

  4. Context Changes: If a context value changes, React may re-render components that use that context.

While not all re-renders are bad, unnecessary re-renders can cause performance issues in our app.

React Profiler

Profiler is a built-in tool for measuring the performance of your React application. It allows you to track how long it takes to render each component, and identify components that are causing performance issues.

Some methods to increase performance are

Using React.memo

React.memo is a higher-order component that you can use to memoize the output of a function component. This means that React will only re-render the component if its props have changed, which can help to improve performance in certain cases.

const MyComponent = React.memo((props) => {
  return (
    <div>
      {props.children}
    </div>
  );
});

export default MyComponent;

Alternatively, we can also write it as

const MyComponent = (props) => {
  console.log('Rendering MyComponent');
  return (
    <div>
      <h1>{props.title}</h1>
      <p>{props.description}</p>
    </div>
  );
};

export default React.memo(MyComponent);

Using Keys

In React, when rendering a list of items, it is important to specify a unique key prop for each item. This is because React uses the key prop to identify which items have changed in the list and need to be updated, added or removed from the DOM.

When a list is rendered without keys, React may have to re-render all of the list items whenever a change is made to the list, even if only one item has been added or removed. This can be inefficient and cause unnecessary re-renders.

By adding a unique key prop to each item in the list, React can identify which items have changed and only update those specific items in the DOM. This can significantly reduce the number of re-renders required and improve performance.

For example, consider a list of items that are rendered in a component:

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

Here, we've added a key prop to each li element based on the id property of the item. Now, when a change is made to the list, React will only update the specific items that have changed, rather than re-rendering the entire list. This can help improve performance and reduce unnecessary re-renders.

useMemo Hook

The useMemo hook is another way to optimize performance in React by memoizing the result of a function call. It's similar to React.memo, but instead of memoizing the entire component, it memoizes only the value that the function returns. This can be useful when you have a function that takes a long time to execute or when you want to avoid recalculating a value unnecessarily.

Here's an example of how to use the useMemo hook:

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

function fibonacci(n) {
  if (n <= 1) {
    return 1;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

function App() {
  const [number, setNumber] = useState(1);
  const fib = useMemo(() => fibonacci(number), [number]);

  return (
    <div>
      <h1>Fibonacci</h1>
      <input type="number" value={number} onChange={(e) => setNumber(Number(e.target.value))} />
      <p>{`Fibonacci(${number}) = ${fib}`}</p>
    </div>
  );
}

export default App;

In this example, we have a function called fibonacci that calculates the nth Fibonacci number. We then use the useMemo hook to memoize the result of calling fibonacci with the current value of number. We pass [number] as the second argument to useMemo, which tells React to only recalculate the value of fib when number changes.

When you run this example, you'll notice that the value of fib is only calculated when number changes. If you try entering a large value for number, you'll see that the calculation takes some time, but subsequent changes to number are much faster thanks to the memoization.

useCallback Hook

The useCallback hook is another commonly used hook in React that helps optimize the performance of functional components. It is similar to the useMemo hook in that it is used to memoize functions, but instead of memoizing a value, it memoizes a function.

The useCallback hook takes two arguments: the first is the function to memoize, and the second is an array of dependencies that should trigger a re-render of the memoized function if they change. The hook returns a memoized version of the function.

Here's an example of how to use the useCallback hook:

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

function fibonacci(n) {
  if (n <= 1) {
    return 1;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

function App() {
  const [number, setNumber] = useState(1);
  const fib = useCallback(() => fibonacci(number), [number]);

  return (
    <div>
      <h1>Fibonacci</h1>
      <input type="number" value={number} onChange={(e) => setNumber(Number(e.target.value))} />
      <p>{`Fibonacci(${number}) = ${fib()}`}</p>
    </div>
  );
}

export default App;

In this example, we use the useCallback hook to memoize the fibonacci function. We pass the number state variable as a dependency to the useCallback hook, which ensures that the fibonacci function is only re-created when the number state variable changes.

We then call the memoized fib function in the JSX using fib(). This ensures that the fibonacci function is only re-executed when the number state variable changes, and not on every re-render of the component.

useTransition Hook

useTransition is a React Hook that lets you update the state without blocking the UI.

We can use the useTransition hook to tell React that a certain state change will result in an expensive rendering. React will then deprioritize this state change allowing other renderings to take place faster providing a very responsive UI. Such expensive renderings are called transition updates and the ones that should take place immediately are called urgent updates. Usually, typing, clicking, and pressing are considered urgent updates as they should provide users with an immediate response to ensure a good user experience.

Call useTransition at the top level of your component to mark some state updates as non-blocking transitions.

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }
  // ...
}

Transitions let you keep the user interface updates responsive even on slow devices.

With a transition, your UI stays responsive in the middle of a re-render. For example, if the user clicks a tab but then changes their mind and clicks another tab, they can do that without waiting for the first re-render to finish.

useDeferredValue

This hook is very similar in functionality to the useTransition hook. While we use the useTransition hook to tell React that a certain setState method will trigger a transition update, we use the useDeferredValue hook to tell React that a received prop value will trigger a transition update.

Call useDeferredValue at the top level of your component to defer updating some part of your UI.

import { useState, useDeferredValue } from 'react';function SearchPage() {  const [query, setQuery] = useState('');  const deferredQuery = useDeferredValue(query);  // ...}

During the initial render, the deferred value will be the same as the value you provided.

During updates, the deferred value will lag behind the latest value. In particular, React will first re-render without updating the deferred value, and then try to re-render with the newly received value in the background.

Summary and further readings

There are several other ways to increase performance that will be discussed later. Some other useful resources are :

https://blog.logrocket.com/optimizing-performance-react-app/

https://www.codementor.io/blog/react-optimization-5wiwjnf9hj

https://www.bacancytechnology.com/blog/react-performance-optimization

Did you find this article valuable?

Support Divij Sehgal by becoming a sponsor. Any amount is appreciated!