A simple solution to optimize React re-renders

Muggle-born
3 min readMay 5, 2021

--

Without using: React.memo, useMemo, PureComponent, or shouldComponentUpdate

In this post, I want to share two alternative solutions to the list of approaches above. Before you reach for one of the methods above you should consider the two solutions below.

Slow Components

Imagine we have a component that takes a long time to render. For this example, we’ll refer to <SlowComponentTree /> as our slow component.

import { useState } from 'react';

export default function App() {
let [color, setColor] = useState('red');
return (
<div>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p style={{ color }}>Hello, world!</p>
<SlowComponentTree />
</div>
);
}

function SlowComponentTree() {
let now = performance.now();
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>;
}

The problem above is that whenever color changes inside App, we will re-render <SlowComponentTree /> which we’ve intentionally delayed to be very slow.

We could use one of the 4 methods listed at the beginning of this post, but that’s not the point of this article. I want to showcase two alternative solutions.

Solution 1: Move State Down

In we take a closer look at the children in App we’ll notice that the color state is only being used by a child of App. Specifically, the <input> and <p> tag:

<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p style={{ color }}>Hello, world!</p>

Lets move state down and extract this part into a separate component:

import { useState } from 'react';

export default function App() {
return (
<div>
<Form />
<SlowComponentTree />
</div>
);
}
function Form() {
let [color, setColor] = useState('red');
return (
<>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p style={{ color }}>Hello, world!</p>
</>
);
}

Now if the color state changes, only the Form component re-renders. Problem solved.

Solution 2: Lift Content Up

The solution above doesn’t work if the piece of state is used somewhere above the expensive tree. For example, let’s say we put the color on the parent <div>

export default function App() {
let [color, setColor] = useState('red');
return (
<div style={{ color }}>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p>Hello, world!</p>
<SlowComponentTree />
</div>
);
}

Now it seems like we cannot “extract” the parts that don’t use color into another component, since that would include the <div>, which would then include <SlowComponentTree />.

See if you can figure out how we could change the code above to prevent re-rendering <SlowComponentTree /> without using one of the 4 methods mentioned at the beginning of this post.

The answer is surprisingly simple:

export default function App() {
return (
<ColorPicker>
<SlowComponentTree />
</ColorPicker>
);
}
function ColorPicker({ children }) {
let [color, setColor] = useState('red');
return (
<div style={{ color }}>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p>Hello, world!</p>
{children}
</div>
);

We create a separate component <ColorPicker> and include the parts that depend on the color state variable. The parts that don’t care about the color state variable are passed to <ColorPicker> using the children prop.

When the color state changes, <ColorPicker> re-renders, but it still has the same children prop it got from App the previous time it rendered, so React doesn’t visit that subtree and doesn’t re-render <SlowComponentTree />.

The important thing to take away from this solution is that if you give React the same element you gave it on the last render, it will not bother re-rendering that element.

Lessons Learned

Before you reach for methods like: React.memo, useMemo, PureComponent, or shouldComponentUpdate; look and see if you can split that parts that change from the parts that do not change.

Don’t neglect moving state down and lifting content up.

Additional Resources

--

--

Muggle-born
Muggle-born

Written by Muggle-born

Hi, my name is Jeremiah. I build things on the web for fun. I enjoy figuring how and why things work the way they do, and I try teaching others along the way.