React Re-rendering: Exploring What, Why, and How

Kuldip Soliya
Simform Engineering
9 min readOct 3, 2023

Understanding the mechanism behind React’s re-rendering process and optimizing performance.

You probably arrived here wondering what dark sorcery or black magic is happening when you modify a component’s status or attributes. The UI refreshes automatically, unlike native Android or iOS app development, where you have to tell it to update based on changes.

To be frank, despite having worked with React professionally for several years, I had never fully grasped the process of re-rendering until now.

I believe this is true for many React developers; they know enough to get by, but if you ask them a question such as “What causes a re-render in React?” you will likely receive various ambiguous answers.

Before we dive deeper, we need to understand the two performance stages of React Native:

  1. Initial Rendering
    This occurs whenever a component is shown for the first time.
  2. Re-rendering
    This refers to subsequent renders of a component that is already visible on the screen. Re-renders are typically triggered when React Native needs to update the app components with new data.

Initial rendering is necessary for every component to display it on the screen, and we cannot do anything to remove it, so we will focus on re-rendering, its common causes, and how to avoid it in React Native.

Re-rendering can happen due to user interactions (e.g., button clicks, changes in text input values), the arrival of external data through asynchronous requests (e.g., API calls, socket connections), or updates from a subscription model (e.g., listeners).

In this article, we will go through the following points:

  • How does rendering work in React Native?
  • How is re-rendering triggered?
  • How can we prevent unnecessary re-rendering?
  • How can we force re-rendering?

How does rendering work in React Native?

React uses a Virtual DOM as a layer between the developer’s description of how the UI should appear and the actual rendering effort performed by the application. To render UI in the browser, the app must modify the Document Object Model (DOM) of the browser.

Definition of VDOM from the official React website:

The virtual DOM (VDOM) is a programming concept where an ideal, or “virtual”, representation of a UI is kept in memory and synced with the “real” DOM by a library such as ReactDOM. This process is called reconciliation.

Virtual Dom to Real Dom Reconciliation | Image by FAM

React reconciliation uses a diffing mechanism. It examines two trees — the existing virtual DOM and the new one — to discover the most effective way to change them. To avoid the slower worst-case situation of O(n³), the aim is to do the comparison in linear time, O(n).

To achieve this efficiency, the algorithm is based on two critical assumptions:

  1. When two elements have different types, they inherently result in different tree structures.
  2. Developers can assist the algorithm by indicating which child elements are likely to remain consistent across multiple renders. This is accomplished by assigning a unique key prop to each child.

By leveraging these assumptions, React can rapidly identify differences between the two trees and apply the necessary updates with precision.

Now, let’s get started with React Native. Instead of rendering to the browser’s DOM, React Native calls native Objective-C APIs for iOS components and Java APIs for Android components, distinguishing it from other cross-platform app development alternatives that often render web-based views.

How is re-rendering triggered?

After the initial rendering has been completed, there are a few different ways to tell React to queue a re-render:

  1. Re-render the component when the state and/or props change
  2. Change in the value of the Context Store
  3. Re-render when change in key prop

1. Re-render the component when the state and/or props change

React prepares to render when component states, props, or any event from the listener changes. Once scheduled, a render occurs. React will find the best re-render time.

React’s setState method updates the state (hooks use useState). The component’s render function will be called, and all of its descendant components will re-render regardless of props or state.

Let’s assume we have this component tree: A>B>C. Now let’s assume we have a counter value incrementing in component B.

Child components re-rendering when parent renders

In the above example, when we call setCounter in component B, React will do the following:

  • We call setCounter in B, which queues a re-render of B.
  • React starts the render pass from the top of the tree.
  • As A is the parent of B, it does not need re-rendering, so React moves ahead in the tree.
  • React sees that B is marked as needing an update and renders it. B returns <C />.
  • C was not originally needed for re-rendering or updating, but as its parent B renders, React will re-render C.

2. Change in the value of the Context Store

Context store value changes are another common cause of re-rendering. Any context value change automatically re-renders all context consumers (useContext).

To avoid this, remove unnecessary context changes and memoize the context store to avoid forcing updates on the entire component tree.

React Context | Image by Dmitri Pavlutin

Let’s understand this by using an example:

Context Store API for state management

In this example, we are using React Context to manage state and share it between components. Here’s how it works:

  1. MyContext is created as a context object.
  2. MyProvider is a component that provides contextual values to its children. It initializes and manages the count state and the increment function.
  3. Various components (ParentComponent1, ParentComponent2, ChildComponent1, ChildComponent2, GrandChildComponent1) access and use the context value to display the count state and trigger the increment function when a button is pressed.
  4. When the increment function is called in any component, it updates the count state in the MyProvider.
  5. All context-consuming components and their children will re-render if they use the updated state.

From the above example, we can see that React’s default behavior is that when a parent component renders, React will recursively render all child components inside of it.

We don’t always need to update a child component since its state or properties don’t change, but the parent component triggers its re-rendering. The next section will cover ways to avoid unnecessary re-rendering.

3. Re-render when change in key prop

A key attribute can affect the rendering of lists. Lists can be SectionLists, FlatLists, or lists you have created by iterating over an array.

When adding a key to a list item, always try to assign one that doesn’t change every time.

For example, you can give item.id as the key. Do not give a randomly generated value, such as a UUID, for the key, as it will change on every render and the diffing mechanism thinks that this is a new element, so trying to re-render every time.

Always update the state and props of a React component when re-rendering it; avoid triggering re-rendering with key props.

How to prevent unnecessary Re-rendering?

First, we should know the difference between necessary and unnecessary re-rendering before learning how to prevent it.

There are two types of re-rendering in React Native:

  • Necessary re-rendering
    After a component’s state or props change, it should refresh itself and related components.

For example, if a component has a TextInput and the user enters text into an input field, each onChange event should update the component’s state and re-render it.

  • Unnecessary re-rendering
    App components in React Native may refresh more frequently than necessary.

For example, if a user types in a TextInput and the screen refreshes with each character, the parent component will re-render all of its children, which is unnecessary and slows down the app.

Most of the time, you can let React handle re-rendering, as it is now fairly clever and quick. React will attempt to batch state variable modifications after the release of version 18 in order to shorten the time it takes for re-rendering to be triggered.

There are methods to prevent React components from re-rendering if it is not necessary.

  • Modifying props or states triggers re-renders in the component and its children, so pass only necessary props and states to components.
  • For more control over re-renders, developers can implement memoization, which ensures re-renders occur only when memoized values (e.g., components, values, functions) change.

React provides two hooks based on memoization to improve performance on re-renders:

  • useMemo()
  • useCallback()

These two hooks allow us to reduce re-rendering counts by caching the result and then returning the same result if the inputs remain unchanged. When the input data changes, the cache is invalidated, and a new return value is generated.

useMemo()

The useMemo hook can be used to prevent expensive and resource-intensive functions from running needlessly.

Let’s understand this by using an example. There are two counters on the screen, along with an expensive time or memory operation. Consider the following code snippet:

When we change the value of countFirst, the component re-calculates the expensive operations. However, even if the value of countSecond changes, the operation function is still executed, which is not ideal.

without useMemo for expensive calculations

To prevent that, we can memoize expensiveCalculatedValue with the help of the useMemo hook.

using useMemo for expensive calculations

Now, if you run this code, expensiveCalculatedValue will only be computed if the countFirst value changes.

useCallback()

The React useCallback hook returns a memoized callback function to isolate resource-intensive functions from being automatically re-rendered.

The useCallback hook only runs when one of its dependencies is updated, which can improve performance.

In this example, we have a component with text and a button. We use this component twice in a parent component. When we press the button in the first component, it changes a value called count and causes a re-render.

This will cause both components to re-render because the functions incrementFirstCount and incrementSecondCount are re-initialized.

passing function as dependencies without using useCallback

To prevent this behavior, we can use useCallback on incrementFirstCount and incrementSecondCount so that they will be re-calculated only if their dependencies change.

With the help of the example below, you can reduce the re-rendering of the second component when invoking the onPress of the first component and vice versa.

passing function as dependencies with using useCallback

React.memo

Only using useMemo and useCallback will not prevent child component re-rendering. To use them effectively, wrap your child component in React.memo.

React.memo is a Higher-Order Component (HOC). HOC is a fancy name for a component that accepts another component as a parameter and returns another component.

React.memo prevents a component from re-rendering if the props (or the values within them) have not changed.

In the above example, you can see that I have wrapped my TextButton component in React.memo, created a new component, TextButtonView , and used that component in the app.

Now, whenever the parent component is re-rendered, useCallback and React.memo work together to not re-render the TextButton component if the onPress function of the TextButton has not changed.

This is the main area where you should use useCallback. When you are passing any function or value as a dependency in the child component, you should wrap that component in React.memo and use useCallback for functions and useMemo for values.

How to trigger Re-rendering ?

There are several ways to manually initiate component re-rendering in React Native.

If you are using a class component, simply callforceUpdate whenever you want to trigger re-rendering.

Calling the forceUpdate will forcibly re-render the component, and thus, it will directly call the render method of the component, skipping the shouldComponentUpdate method.

Class component re-rendering with forceUpdate

For functional components, you have to use useReducer to trigger a re-render.

useState is a basic hook for managing simple state transformations, and useReducer is an additional hook for managing more complex state logic.

However, it is worth noting that useState uses useReducer internally, implying that one could use useReducer for everything that can be done with useState.

const [_, forceRender] = useReducer((newVal) => newVal+ 1, 0);

By using above useReducer hook, you can achieve re-rendering.

Functional component re-rendering using useReducer

Why did you render

To assist in managing and optimizing re-rendering, developers can rely on the Why Did You Render package. This useful tool offers valuable insights into the reasons behind unnecessary re-rendering of components.

By providing detailed information on why a specific component has been re-rendered, developers can identify and resolve any redundant rendering cycles, empowering them to fine-tune the performance of their applications and deliver an optimized user experience.

Key Takeaway

  • Understanding re-rendering in React Native is essential for optimizing performance and providing a seamless user experience.
  • By understanding rendering concepts, identifying triggers, and implementing strategies to prevent unnecessary re-renders, developers can optimize their code and improve efficiency.
  • Tools like “Why Did You Render” provide valuable insights for optimization.
  • With a solid understanding of re-rendering, developers can build high-performing React Native apps that efficiently update the UI while minimizing rendering cycles.

To gain a better understanding of the underlying mechanism of the rendering process, please refer to the following articles:

  1. https://reactnative.dev/architecture/render-pipeline
  2. https://dev.to/teo_garcia/understanding-rendering-in-react-i5i
  3. https://reactjs.org/docs/faq-internals.html

Read more about useMemo, useCallback, and React.memo in the following articles:

  1. https://react.dev/reference/react/useMemo
  2. https://react.dev/reference/react/useCallback
  3. https://react.dev/reference/react/memo

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow Us: Twitter | LinkedIn

--

--