Easily optimise your React (Native) Components rendering

Ovidiu Latcu
Corebuild Software
Published in
6 min readFeb 25, 2020

Goal: this article aims to make it clear how React rendering works, and how you can easily reduce the number of renders your components do, and automatically improve your application / UI performance.

Note: you don’t need to go into optimisations too early, or if you think you don’t need it (remember, “early optimisation is the root of all evil”) but it doesn’t hurt to fully understand from the beginning how React render() works, especially since some of those optimisations are fairly easy to achieve, and they can be implemented as coding guidelines in your projects.

Note2: I’m using in my example ReactNative, because I do most of my work in ReactNative 🙈, but the same applies for React on the web. (right? 😂)

So, how does React render() work ?

The render() method gets called as we know each time props or state change on our component. This will then trigger a render() on all the children components (if they are not optimised). React will apply the reconciliation algorithm, and determine if some new components have to be rendered, unmount components that are not needed anymore, or update some of the existing components. BUT, a render() will be triggered in all the child-components, React will still perform the diffing, even if in the end it will decide that this component, and all it’s subcomponents don’t need to be updated at all.

So as you can imagine, if you have a complex view hierarchy, and also a lot of state or props updates, triggered by various events, or redux store updates, this can result in a lot of unnecessary renders and computations.

To make this clearer, let’s take a simple example. Let’s create the classic ‘React counter’ example, but let’s adjust it for our purposes, and let’s have 2 counters instead of 1. We will create a Counters component to hold the state of our 2 counters and 2 buttons to increment the state, and let’s make a CounterDisplay component that just displays the each counter.

(I will be using class components and pure JS instead of Typescript just to keep the examples clear and easy to understand. I will at the end of the article provide also the code that uses functional components using hooks)

So a lot of people would expect here, if we press the first button, to increase just counter1 that just our first CounterDisplay gets re-rendered, but in fact both our CounterDisplay will get re-rendered (and all other child components of our Counters component)

Orange components will get rendered

As said before, this shouldn’t be a problem for such a small component tree, but if our CounterDisplay components would be more complex and instead of 2 we have 20–30 , this can easily affect performance. So let’s see how we could optimise in our case our CounterDisplay to avoid un-necessary renders.

Optimisation 1: extend PureComponent

We should make our CounterDisplay extend from PureComponent .

PureComponents automatically implement shouldComponentUpdate() and will not re-render itself and subsequently all it’s children if it does not detect a change in state or props.

Warning: PureComponent will do a shallow check on your props and state, so make sure you do not mutate those, because your components will not re-render.

If after we extend from PureComponent and we now increment counter 1, you will see that just the first CounterDisplay will get re-rendered.

Orange components will get rendered

Ok, we have now our brand new optimised CounterDisplay but we want to make it prettier. Let’s add some styles to it :

Ooops! We broke our component again. Every render() in the parent component will trigger re-renders on both our CounterDisplay components. Can you figure out why ?

Optimisation 2: use StyleSheet for styles

On every render() we are creating a new style that we pass to our CounterDisplay component. Even though the style is exactly the same, it will be a new object, and our component will be re-rendered. So instead we should create a StyleSheet which will preserve our styles.

Great, now our component it’s fixed. Let’s go ahead and for the sake of this article make our CounterDisplay component interactable. Let’s adjust it so that it will take an onPress function, which will be invoked with the counter value.

Well what do you know ? We ruined our optimisations again.

Optimisation 3: avoid passing arrow functions to components

With our new callbacks in place, during every render() inside the parent component Counters we are creating a new arrow function that we pass as a property down to our CounterDisplay components which will cause them to re-render because React detects that it received a ‘new’ property, even though the new function is exactly the same as the previous one.

So, we should instead create our callback as a constant, or a function reference in our parent Counters component.

Now, each time we render() the Counters component, we will pass down as an onPress callback the same function, so we won’t trigger un-necessary renders in our CounterDisplay components.

Optimisations when using Hooks

Now let’s take the same example but re-write it using hooks. Our code should look something like this.

(I already created the StyleSheet as that is straightforward, and the same when using hooks as well)

With our new functional components that use hooks, now we have the same issues. Too many un-necessary renders are performed. Let’s see how we can fix them.

Hooks optimisation 1: React.memo

As seen in the docs , React.memo is the same as using PureComponent , but for function components. Just what we needed ! Who would have thought !? 😆

The update is simple, just wrap our CounterDisplay function with React.memo

const CounterDisplay = React.memo(props => {
...
});

Now our component CounterDisplay shouldn’t re-render if props don’t change, but the problem is that we still pass a new onPress function on each render() from our parent Counters component. Let’s also fix that.

Hooks optimisation 2: React.useCallback

To ‘memoize’ our callbacks, we can use the useCallback function which will return a ‘memoized’ callback as described in the docs.

The code to do this for our component will look something like this:

const onCounterPress = useCallback(value => alert(value), []);

That’s it 👏 ! Now our functional components are also performant as their class components counter-parts.

If you want to play around with the code, you can find it in this Expo snack. 💻

Conclusions

First and foremost, remember : “Early optimisation is the root of all evil” .

But, if you feel your app performance can be improved, you should start profiling it and if you feel like components re-rendering affects your app performance you can start looking into optimising some of them. You might also find useful this npm package that can notify you about un-necessary, avoidable re-renders.

Also, if you are in the beginning of your project, some of those things are not that hard to do, and don’t add too much complexity, so you could probably include them in your project coding guidelines.

Let me know if this clarified some of the miss-conceptions about React rendering, and if all the suggestions are clear and make sense to you. 🙏

--

--