Maximizing Performance: A deep dive into React re-renders and how to optimize your components

Olasunkanmi Balogun - Jan 27 '23 - - Dev Community

React is a powerful tool for building complex user interfaces, but as the size of your application grows, so do the performance issues. One of the most common performance issues developers encounter is unnecessary re-renders caused by a component's state or props changing too many times. In this article, we will take a deep dive into React re-renders, and explore techniques for maximizing the performance of your components. From understanding when and why a component re-renders, to implementing best practices such as React.memo, this article will provide you with the knowledge you need to optimize the performance of your React applications, we'll explore the intricacies of React re-renders and reveal how to make your components perform at their best. Let's embark on this adventure together and uncover the secrets of creating high-performing React applications.

Table of Contents

  1. Importance Of Performance In React.
  2. Understanding React Re-renders.
  3. Common Antipatterns And Best Practices To Improve Performance.

Importance Of Performance In React

React is one of the most popular front-end libraries, and as it's widely used in building dynamic and interactive web applications, it's crucial to ensure that the performance of React applications is optimized to provide a smooth and responsive user experience. Performance is an important part of any application and poor performance can lead to slow page load times, laggy user interactions, slow development time, insecurity, and a poor overall user experience.

Understanding React Re-renders

When thinking about a React application's performance, there are two stages we should really be concerned about, they are:

The initial render stage which generally happens when the component appears on the screen.

The re-render stage which is the second or subsequent render that happens after the component is already on the screen. Re-renders happen when there's an update in a React's components state. An update can occur as a result of a user's interaction with the site or a component making asynchronous requests.

Re-renders can take a toll on an application's performance if there are too many unnecessary re-renders. Sparingly, unnecessary re-renders are not a problem since React engines are very fast and can deal with re-renders without being noticeable. However, an application more than adequately re-rendering could result in performance issues such as laggy user interactions as aforementioned, or even break the app completely. Let's take a look at some reasons that could cause re-renders in React. A component re-renders when:

  1. When State Updates
  2. When Parent Component re-renders
  3. When Props Update

When State Updates

When the state of a component is updated, the component re-renders itself to reflect the update made to the state. Let's take a look at the code illustration below

See practical example on CodeSandbox.

In the above example, when the button is clicked, state is updated. Hence, causing the whole component to re-render.

When Parent Component Re-renders

Naturally, when the parent of a component re-renders i.e when its state is updated, it causes a not memoized child component to also re-render whether its props have changed or not.

See practical example on CodeSandbox..

We saw on the console that the child component also re-renders even when the prop, name, doesn't change.

When Props Update

Re-render due to props update only matters when memoization methods such as React.memo are used. That way, child component re-renders are restricted to only when its prop values update.

Common Antipatterns And Best Practices To Improve Performance

We will now explore the common antipatterns that developers make when working with React that can lead to unnecessary re-renders in a React application, we will also look at best practices for optimizing the performance of React components, and tips for identifying and preventing unnecessary re-renders. Let's dive in...

Preventing Re-renders With Compartmentalization

Compartmentalization is the practice of breaking down a complex component into smaller, more manageable components.

Apart from the fact that this helps to improve the code's maintainability and reusability and makes it easier to think about the component's behavior, this can also help us prevent unnecessary re-renders. Let's see how...

See how compartmentalization can help prevent unnecessary re-renders on CodeSandbox.

We see how by compartmentalization, the compartmentalized component prevents the re-rendering of the whole component, thereby preventing random component from re-rendering every time the button is clicked.

Nested Components

It is generally bad practice to nest components. Nesting components in another component can make your app really slow.

Assuming there is an update in the state of the parent component causing it to re-render, the nested component doesn't just re-render with the parent but also re-mounts i.e React destroys the component and then re-create it all over again from scratch. This is a process that is going to be slower than a normal re-render given the circumstances. Assuming the nested component makes a request to an API with useEffect without any dependency, then the function will be triggered every time the parent re-renders. This can cause bugs you don't want.

See a practical on CodeSandbox

In order to prevent this, child components should be written separately to avoid being left in a performance hole.

Passing Children As Props

Passing children as props is somewhat like the concept of compartmentalization afore explained. The difference is that the component that updates the state is extracted and is used to wrap the child component which is passed to it as children. That way, the parent component sees the children as props, therefore child components won't be re-rendered.

See practical example on CodeSandbox

Preventing Re-renders With React.memo And useMemo

As your React application grows in complexity, it is important to optimize components to prevent performance issues.React.memo helps us to ensure that a component re-renders only when necessary. React.memo is a higher-order component that allows us to wrap functional components and only re-render them when their props have changed.

See practical example on CodeSandbox..

However, note that interestingly, if the child component has a prop that is not a primitive type (i.e if they are not strings, numbers, or boolean) then memoizing your component with React.memo alone won't be enough to prevent your component from re-rendering even though its prop has not changed unfortunately. Check out Alex Sidorenko's article to read more as to why this is. Fortunately, there is a way we can work around this and that's where useMemo comes in handy. With useMemo hook you can memoize the non-primitive prop to prevent the component from re-rendering unless the prop updates.

See practical example on CodeSandbox.

Improving Performance Of Lists

One of the most important aspects of working with lists in React is using keys. Keys are unique identifiers that help React keep track of which items in a list have been added, removed, or moved.

The algorithm for the re-render phase of lists using keys in React involves generating "before" and "after" snapshots of the elements, identifying existing elements to be re-used, using the "key" attribute to match items with the same "before" and "after" key and defaulting to using sibling indexes as the key if none is provided, unmounting items that no longer exist, creating new items, and updating existing items.

Without keys or appropriate use of keys, React will have a hard time determining which elements have changed, resulting in unnecessary re-renders, unnecessary re-mounts, and poor performance. However, we should not just provide keys just for the sake of providing keys to the list, it doesn't guarantee the improvement of performance in lists.

We will now discuss what's considered best practice and what's considered an antipattern.

Index As Key

It is okay to use the index of the array as key, however, this depends on if the array is constantly being modified or not i.e they are static. Generally, It is considered best practice to provide strings that are consistent between re-renders as keys. Learn more about this here.

See practical example for static list on CodeSandbox

See practical example for dynamic list on CodeSandbox

Random values as key

Providing random values as key is a very big performance killer given how the algorithm to re-render lists in React works. Rather than the items just re-rendering on every state update, React re-mounts the items all over again. This can really slow down our app.

See practical example on CodeSandbox

Conclusion

In conclusion, maximizing performance in React is crucial for creating smooth and responsive user experiences. By understanding the re-render process and using techniques such as React.memo and useMemo, as well as implementing strategies like using keys appropriately to avoid unnecessary re-renders, you can ensure that your React applications run at optimal speed. Remember to also keep an eye out for common antipatterns and to constantly monitor and test your application's performance. With the knowledge and techniques discussed in this article, you can take your React development skills to the next level and create high-performing, efficient applications.

. . . . . . . . . . .