Sparsh Malhotra

Sep 15, 2024 • 5 min read

A Guide to React Rendering Behaviour - Part 3

A pattern-based guide that you can reference on a regular basis when writing components or fixing performance problems.

A Guide to React Rendering Behaviour - Part 3

This is the third part of the series where we dive deep into the concept of re-rendering in React. If you haven't checked out parts one and two yet, do yourself a favour and give 'em a read. Here are the links:

  1. A Guide to React Rendering Behaviour - Part 1

  2. A Guide to React Rendering Behaviour - Part 2

Today, we're cutting straight to the chase - no fluff, no long explanations, just the good stuff. We're talking patterns to stop your React app from re-rendering itself into oblivion. You will or have already encountered these patterns a lot if you are a React developer.

Preventing re-renders with composition

❌ Creating components in render function

Creating components inside render function of another component is an anti-pattern that can be the biggest performance killer. On every re-render React will re-mount this component (i.e. destroy it and re-create it from scratch), which is going to be much slower than a normal re-render. On top of that, this will lead to such bugs as:

  • Possible content flashing during re-renders

  • State reset in the component with every re-render

  • Loss of focus for focused components

  • useEffect with no dependencies triggered on every re-render

✅ Moving the state down

When a large component contains a small UI element that has its own state, move that state and UI logic into a separate, smaller component. A typical example would be opening/closing a modal with a button click in a complicated component that renders a significant portion of a page.

In this case, the state that controls modal dialog appearance, dialog itself, and the button that triggers the update can be encapsulated in a smaller component. This prevents the entire large component from re-rendering when only the small element needs to update.

✅ Children as props pattern

This can also be called “wrap state around children”. This pattern is similar to “moving state down”: it encapsulates state changes in a smaller component. The difference here is that state is used on an element that wraps a slow portion of the render tree, so it can’t be extracted that easily.

A typical example would be onScroll or onMouseMove callbacks attached to the root element of a component. In this setup, we move the state handling and state-dependent parts into a smaller, focused component. The complex, slower-to-render content becomes this component's children. Since children is just another prop to the smaller component, it stays unaffected by state changes. This means the bulky content doesn't re-render when the state updates, keeping things smooth and efficient.

✅ Components as props pattern

This pattern is similar to "children as props," but instead of passing complex content as children, we pass entire components as props to a state-managing wrapper. This approach isolates state changes in a smaller component, preventing unnecessary re-renders of the heavier components that are passed as props.

Use this pattern when you have multiple independent, heavy components that don't need to be aware of a particular state, but can't be grouped together as a single children prop.

Preventing re-renders caused by Context

Memoizing Provider value

When using Context in React, it's crucial to memoize the Provider's value if it's not at the app's root. This prevents unnecessary re-renders of all consumers when the Provider's parent component updates. By memoizing the value, you ensure that the context only triggers re-renders when the value actually changes, not on every render of the parent component.

Splitting data and API

When a Context contains both data and API (setters/getters), split them into separate Providers under the same component. This approach prevents unnecessary re-renders of components that only use the API, as they won't be affected by data changes. It's an effective way to optimize performance in applications with complex state management needs.

Preventing re-renders with React.memo

✅ Component with primitive values as props

React.memo is a higher-order component that memoizes your component, preventing re-renders if its props haven't changed. For optimal performance, all non-primitive prop values (objects, arrays, functions) should be memoized as well, typically using useMemo or useCallback.

✅ React.memo: components as props or children

When using React.memo with components passed as props or children, apply it to these passed components, not the parent. Memoizing the parent won't prevent re-renders because children and props are objects that change on each render. Memoizing the passed components ensures they only re-render when their specific props change.

Preventing re-renders with useMemo

✅ Using useMemo for expensive calculations

One of the use cases for useMemo is to avoid expensive calculations on every re-render. useMemo has its cost (consumes a bit of memory and makes initial render slightly slower), so it should not be used for every calculation. In React, mounting and updating components will be the most expensive calculation in most cases.

As a result, the typical use case for useMemo would be to memoize React elements. Usually parts of an existing render tree or results of generated render tree, like a map function that returns new elements.

Improving re-render performance of lists

In addition to the regular re-renders rules and patterns, the key attribute can affect the performance of lists in React.

Value in key should be a string, that is consistent between re-renders for every element in the list. Typically, item’s id or array’s index is used for that. It is okay to use array’s index as key, if the list is static, i.e. elements are not added/removed/inserted/re-ordered.

❌ Don't use random value as key

Randomly generated values should never be used as values in key attribute in lists. They will lead to React re-mounting items on every re-render, which will lead to:

  • very poor performance of the list

  • bugs if items have state or any uncontrolled elements (like form inputs)

Join Sparsh on Peerlist!

Join amazing folks like Sparsh and thousands of other people in tech.

Create Profile

Join with Sparsh’s personal invite link.

0

9

0