How to optimize and improve performance in React Native — Part 1

Goxo Tech
6 min readJul 2, 2021
How to Optimize React Native

Imagine you have an awesome mobile application, with all the features your clients want, and then… you keep getting reports about your application being slow and even impossible to use.

And people won’t care about the features the app has, they don’t like it because it is too slow, they prefer to use the other alternatives in the market, even if they have fewer features.

Maybe you shouldn’t have used React Native to build the app. Maybe you should create an app using Kotlin and Swift and forget about this shiny thing called React Native.

Well, no, that is not the case. Well, you could use those languages but that is not the point of this article.

Today, we will start a series of articles about how to optimize React Native apps to perform way faster. In fact, we use all these techniques in the apps created in Goxo.

Using Hermes

This will be the very first piece of advice. Hermes is an open-source JavaScript engine optimized for React Native.

It improves a lot the start-up time, it also decreases the memory usage and even the app size is smaller.

Right now, this engine is an opt-in feature so it is not enabled by default. Also, it is not available for iOS until React Native version 0.64.

I would recommend you to upgrade your React Native version to that one (or greater) not only for this feature but for all the improvements they have. Also, you have a really good guide in the React Native docs.

After that, you will be able to enable Hermes as explained in this other guide: https://reactnative.dev/docs/hermes

Release builds

how to create a release build in react native

Builds in release mode have lots of performance issues since they are focused on giving feedback to the developer, not running fast and smoothly.

So make sure to always build (and test the performance) in production mode (when releasing the app to your clients/users). Probably you already do this, but it is always worth it to double-check it: https://reactnative.dev/docs/running-on-device#building-your-app-for-production

Console.log

Console.log, Console.warn, and so on can cause a big bottleneck in the JavaScript thread so make sure you remove all those calls in your production build.

There several ways to do it without having to search for each line where they are called in your code but my favorite is to use a babel plugin that removes all those calls. You might want to check it here: https://babeljs.io/docs/en/babel-plugin-transform-remove-console/

Beware of “Slow Navigator Transitions”

We have several lists in our apps and they are animated, so the user has better feedback of their actions.

But the Animated API calculates each keyframe on-demand on the JavaScript thread so it was causing a lot of delays when running animations.

What is the solution? Really easy! Just make sure to set the property useNativeDriver as true.

Optimizing FlatList components

This was our biggest issue. As we said before, we have lots of lists with custom components and they are re-rendered all time due to server updates.

That was causing lots of performance issues in our apps (even in modern devices!).

So let’s talk about some configurations we did to make it way faster (in fact now we don’t have any delay in our apps, even in old devices!).

removeClippedSubviews

This a property we can set and then views outside the viewport will be detached. It reduces the time spent on the main thread (so it reduces the risk of dropped frames).

But it can cause some weird bugs (as notices in the official documentation) mainly on iOS.

So instead of setting it as true, I prefer to set it like this:

removeClippedSubviews: Platform.OS===’android’

initialNumToRender

This one, as the name says, sets the number of items to render when the FlatList is loaded initially.

I like to set this as the maximum number of items that would fill the viewport plus 1–2 more.

So, for example, if my FlatList has enough space to render 3 items at the start, I will set initialNumToRender as 5.

You should play a little with this number since setting a low initialNumToRender may cause blank areas.

windowSize

This property defines the viewport as 1. Then you set a number here with the maximum number of items (in viewport’s amounts) to render outside of the visible area.

For example, if our viewport has enough space to handle 5 items, and we define this property as 5, React Native will render your current viewport with 5 items, and will also render 2 viewports above it (if possible) (with a total of 2*5 items) and 2 viewports below it (if possible) (with a total of 2*5) items.

So, setting a lower number will help to improve the render time but at the same time, it can generate blank spaces if you scroll faster than React Native can load new viewports.

The default number is 21 so you can play around with that number. In our case, we have it around 7, since our components are really big (so in 1 viewport we have around 4 items).

maxToRenderPerBatch and updateCellsBatchingPeriod

Now we will talk about two properties that I had to explain them together since you have to play with how they affect each other.

maxToRenderPerBatch is a property that determines how many list items will be rendered on each scroll (the default value is 1). So the higher you set this value, the higher the fill rate but worse performance you will have.

Meanwhile, updateCellsBatchingPeriod adjust the time between each batch (in ms) (the default value is 50).

So with those properties you can achieve your desired ratio between a good fillrate and a good performance (since lowering the upDateCellsBatchingPeriod will also impact the performance).

renderItem

The renderItem property pass a function to the FlatList component and it is used to render each one of the items in the list.

You usually set it like this:

<FlatList
....
renderItem={({item})=><View></View>}
....
/>

But having an anonymous function there is not good for the performance, since it has to be “regenerated” each time a new item needs to be rendered. It is way better to declare the render item outside, like this:

render()
{
const renderItemFunction = ({item}) => {
return (
<View></View>
);}return (
<FlatList
....
renderItem={renderItemFunction}
....
/>);
}

keyExtractor

Finally, make sure you always set the key of each item, for example using the keyExtractor property.

For example, let’s say our items have all of them an unique _id property. Then you can configure this property like:

keyExtractor={(item,index)=>item._id}

Setting a key is really important, it improves a lot the performance and it is a good practice. You can read more about the importance of keys in React (and React Native) here.

Is this all?

No! Not even closer! There are a lot more optimizations we can (and we should) do in our React Native projects but I think it is better to separate this article in several parts, so it is easier to read.

In the next one, we will explain how to determine when a component should render and how to optimize that behaviour. Finally, we will explain you how to “memoize” components. And that is something really important since it helped us to improve one of our lists from taking around 2 seconds to load into just 0.1 seconds! (Even without the other optimizations!).

So stay tuned and don’t forgot to follow this blog because the next part of this series will be really interesting for us as React Native developers!

— This article was written by Alberto Del Águila, Full Stack Developer on Goxo

--

--

Goxo Tech

We are a Spanish startup building the next generation of the direct channel for restaurant businesses. We write about tech and product. https://goxoapp.com