Vinícius Vidal

Full-Stack Developer

What is a re-render in React

Every re-render is caused by a state change.

image.png

Why does a component render again?

Roughly speaking, the main role of React is to make sure the UI is always up-to-date according to the application state. When something in the state changes, React does a re-render to figure out exactly what needs to be changed on the screen. That’s how it keeps everything in sync.

We can break this process into 3 steps:

  1. Triggering: Something changes (state, props).
  2. Rendering: React recalculates the UI.
  3. Committing: Applies the changes to the DOM.

Now in practice: let's imagine a counter and a button to increment it. When the first render happens (called the initial render) the counter starts at 0. Now, when the user clicks the button, the state will change from 0 to 1. As said before, due to the state change, a re-render is triggered: triggering stage. React then executes the code again using the new state value: rendering stage.

Each render is a snapshot, like a picture, which is a skeleton of how the UI should be structured according to the current state of the app.

So now we have two snapshots: one with the counter at 0 and another with it at 1.

React then compares these two snapshots and checks what changed between them, and then updates the DOM: committing stage. React only updates the DOM (UI) if there are differences between the renders.

image.png

In the image above, the components in green are the ones that went through a re-render. We can see that when the “Counter” component has a state change, both it and all its children get re-rendered. In this example, there's only one direct child, but this applies to the whole tree of descendant components, no matter how deep: if a parent component re-renders, all its children will re-render too by default.

So, do props cause re-renders?

You’ve probably heard that changes in props trigger a re-render, but the point is, when a component re-renders, all its descendants go through it too.

Let’s imagine the following structure:

image.png

The components in green are the ones that re-rendered after the count state changed. But... why did the Description component also re-render? 🤯

The answer is simple: when a component re-renders, all its children go through the re-render process too.

You might be thinking: “but the Description component doesn’t even receive the count prop.” A fair question. But what happens is that React does a recursive re-render in all descendant components, regardless of whether they receive props directly or not. Just remember: re-rendering doesn’t necessarily mean updating the DOM. React will still compare the snapshots (with the Virtual DOM) to decide what actually needs to be changed on screen.

Pure components

A pure function is one that, given the same input, always produces the same output. Its result depends only on the values it receives as input.

The same goes for pure components. It is those that we can guarantee will always return the same result.

Here’s how to do that:

function HeaderComponent() {
  return (
    <h1>My Blog</h1>
  );
}

export const Header = React.memo(HeaderComponent);

This way, we’re basically telling React: “Trust me, bro!”. With this, React stops re-rendering that specific component every time the parent re-renders. Pure components (like the ones created with React.memo, for example) will only re-render if their props actually change, and that’s where we can avoid unnecessary re-renders. This technique is called memoization.

You might be imagining why this isn’t the default behavior in React, but the thing is, re-renders are not that expensive, and using the memoization technique without a second thought can actually be worse, since it has to keep checking if the props have changed.

Here’s the proof:

image.png

As complex as that code may seem, I’ll simplify it: the shallowEqual function basically uses Object.is to compare the old props with the new ones. If all of them are equal, React does a bailout, meaning it “gives up” on re-rendering that component because it knows nothing changed.

And if it were the right thing to apply this technique to all components, it would be the default behavior of React.

How context behaves

The behavior doesn’t change much when it comes to React context. All children of a context provider will still go through a re-render when the context state changes.

When a pure component consumes a context, React is able to detect that and trigger re-renders when the context state changes, as if they were “invisible props.” Example:

function SignedComponent() {
  const { user } = React.useContext(AuthContext);

	 if (!user) {
		 return 'Entrar';
	 }

  return 'Bem vindo!!!'
}

export const Signed = React.memo(SignedComponent);

In this example, user is a dependency of the Signed component, and every time it changes, it will trigger a re-render.

And that’s it!

Hope you now understand what a render is in React.

See you next time, dev! 👋