How Can Higher-Order Components Improve Performance of React Native Apps?

In short

This article, part of The Ultimate Guide to React Native Optimization, emphasizes the crucial role of using specialized components for optimal app performance. Specialized components, such as FlatList and FlashList, significantly impact performance, providing smoother scrolling and efficient rendering of large datasets. By adopting these components, developers not only enhance user experience but also save time by leveraging default support for common UI patterns, ultimately increasing the overall efficiency and ROI of their React Native applications.

Originally published in December 2021, updated in March 2024.

Why you should use dedicated higher-ordered components

Using specialized components will always make your app work as fast as possible. It has a significant impact on the performance of your app which leads to better user experience.

Going further, by applying the practices described in the following article, you will increase the revenue-generating efficiency of your products and increase their ROI.

What’s more, in this article we will tell you how to save yourself a lot of time reimplementing the most common UI patterns from the ground up. Thanks to that, you will be able to implement future updates faster.

Primitive and high-order components in React Native

In a React Native application, everything is a component. At the end of the component hierarchy, there are so-called primitive components, such as <rte-code>Text<rte-code>, <rte-code>View<rte-code> or <rte-code>TextInput<rte-code>. These components are implemented by React Native and provided by the platform you are targeting to support the most basic user interactions.

When we're building our application, we compose it out of smaller building blocks. To do so, we use primitive components. For example, in order to create a login screen, we would use a series of <rte-code>TextInput<rte-code> components to register user details and a <rte-code>Touchable<rte-code> component to handle user interaction. This approach is true from the very first component that we create within our application and holds <rte-code>true<rte-code> through the final stage of its development.

On top of primitive components, React Native ships with a set of higher-order components designed and optimized to serve a certain purpose. Being unaware of them or not using them can potentially affect your application performance, especially as you populate your state with real production data. Your app's bad performance may seriously harm the user experience. As a consequence, it can make your clients dissatisfied with your product and turn them towards your competitors.

How high-order components affect your React Native app’s performance

If you're not using specialized components, you are opting out of performance improvements and risking a degraded user experience when your application enters production.

It is worth noting that certain issues remain unnoticed while the application is developed as mocked data is usually small and doesn't reflect the size of a production database. Specialized components are more comprehensive and have a broader API to cover than the vast majority of mobile scenarios.

Always use specialized components such as FlatList for lists

Let’s take long lists as an example. Every application contains a list at some point. The fastest and dirtiest way to create a list of elements would be to combine <rte-code>ScrollView<rte-code> and <rte-code>View<rte-code> primitive components.

However, such an example would quickly become problematic when the data grows. Dealing with large data sets, infinite scrolling, and memory management was the motivation behind <rte-code>FlatList<rte-code> – a dedicated component in React Native for displaying and working with data structures like this.

Compare the performance of adding a new list element based on <rte-code>ScrollView<rte-code>:

to a list based on <rte-code>FlatList<rte-code>.

The difference is significant, isn’t it? In the provided example of 5000 list items, the <rte-code>ScrollView<rte-code> version does not even scroll smoothly.

At the end of the day, <rte-code>FlatList<rte-code> uses <rte-code>ScrollView<rte-code> and <rte-code>View<rte-code> components as well - what’s the deal then?

Well, the key lies in the logic that is abstracted away within the <rte-code>FlatList<rte-code> component. It contains a lot of heuristics and advanced JavaScript calculations to reduce the number of extraneous renderings that happen while you’re displaying the data on screen and to make the scrolling experience always run at 60 FPS.

Just using <rte-code>FlatList<rte-code> may not be enough in some cases. <rte-code>FlatList<rte-code> performance optimizations relay on not rendering elements that are currently not displayed on the screen. The most costly part of the process is layout measuring. <rte-code>FlatList<rte-code> has to measure your layout to determine how much space in the scroll area should be reserved for upcoming elements. 

For complex list elements, it may slow down interaction with a flat list significantly. Every time <rte-code>FlatList<rte-code> will approach to render the next batch of data, it will have to wait for all new items to render to measure their height.

However, you can implement <rte-code>getItemHeight()<rte-code> to define element height upfront without the need for measurement. It is not straightforward for items without constant height. You can calculate the value based on the number of lines of text and other layout constraints.

We recommend using <rte-code>react-native-text-size<rte-code> library to calculate the height of the displayed text for all list items at once. In our case, it significantly improved responsiveness for scroll events of <rte-code>FlatList<rte-code> on Android.

FlashList as a successor to FlatList

As already discussed, <rte-code>FlatList<rte-code> dramatically improves the performance of a huge list compared to <rte-code>ScrollView<rte-code>. Despite proving itself as a performant solution, it has some caveats.

There are popular cases where developers or users have encountered, for instance, blank spaces while scrolling, laggy scrolling, and a list not being snappy, almost on a daily basis. <rte-code>FlatList<rte-code> is designed to keep certain elements in memory, which adds overhead on the device and eventually slows the list down, and blank areas happen when <rte-code>FlatList<rte-code> fails to render the items fast enough.

We can, however, minimize these problems to some extent by following the guide on optimizing FlatList configuration, but still, in most cases, we want more smoothness and snappy lists. With <rte-code>FlatList<rte-code>, the JS thread is busy most of the time, and we always fancy having that 60FPS tag associated with our JS thread when we’re scrolling the list.

So how should we approach such issues? If not <rte-code>FlatList<rte-code>, then what? Luckily for us, the folks at Shopify developed a pretty good drop-in replacement for <rte-code>FlatList<rte-code>, known as <rte-code>FlashList<rte-code>. The library works on top of RecyclerListView, leveraging its recycling capability and fixing common pain points such as complicated API, using cells with dynamic heights, or first render layout inconsistencies.

How FlashList works

<rte-code>FlashList<rte-code> recycles the views outside the viewport and re-uses them for other items. If the list has different items, <rte-code>FlashList<rte-code> uses a recycle pool to use the item based on its type. It’s crucial to keep the list items as light as possible without any side effects; otherwise, it will hurt the performance of the list.

There are a couple of props that are quite important with <rte-code>FlashList<rte-code>. First is <rte-code>estimatedItemSize<rte-code>, the approximate size of the list item. It helps <rte-code>FlashList<rte-code> to decide how many items to render before the initial load and while scrolling. If we have different-sized items, we can average them. We can get this value in a warning by the list if we do not supply it on the first render and then use it forward. The other way is to use the element inspector from the dev support in the React Native app.

The second prop is <rte-code>overrideItemLayout<rte-code>, which is prioritized over <rte-code>estimatedItemSize<rte-code>. If we have different-sized items and we know their sizes, it’s better to use them here instead of averaging them. 

FlashList metrics

Let’s talk about measuring <rte-code>FlashList<rte-code>. Remember to turn on release mode for the JS bundle beforehand. <rte-code>FlashList<rte-code> can appear to be slower than <rte-code>FlatList<rte-code> in dev mode. The primary reason is a much smaller and fixed <rte-code>windowSize<rte-code> equivalent. We can leverage <rte-code>FlashList<rte-code>’s built-in call-back functions to measure the blank area <rte-code>onBlankArea<rte-code> and list load time <rte-code>onLoad<rte-code>. You can read more about available helpers in the Metrics section of the documentation.

We can also use Bamlab's Flashlight, which gives us the results for FPS on the release builds in the form of a performance report.It also creates a nice-looking graph of CPU usage over the period of profiling, so we can verify how certain actions affect this metric. For now, Flashlight supports Android only, but the team is working on supporting iOS.

graph showing CPU usage
Performance report from Bamlab’s Android Performance Profiler
With Flashlight there is no need to install anything in your app, making this tool even easier to use. It can also measure the performance of production apps and generate very handsome-looking web reports which include: Total CPU usage, CPU usage per thread, and RAM utilization.

There are 2 ways of using Flashlight – you can run it locally: 

  • <rte-code>curl https://get.flashlight.dev | bash<rte-code> 
  • or in the cloud with flashlight.dev

Thanks to using specialized components, your application will always run as fast as possible. You can automatically opt-in to all the performance optimizations performed by React Native and subscribe for further updates. At the same time, you also save yourself a lot of time reimplementing the most common UI patternsfrom the ground up, sticky section headers, pull to refresh – you name it. These are already supported by default if you choose togo with <rte-code>FlashList<rte-code>.

Need help with performance optimization?

We are the official Meta partners on React Native. We’ve been working on React Native projects for over 5 years, delivering high-quality solutions for our clients and contributing greatly to the React Native ecosystem. Our Open Source projects help thousands of developers to cope with their challenges and make their work easier every day.

Contact us if you need help with cross-platform or React Native development. We will be happy to provide a free consultation.

Latest update:
March 26, 2024

FAQ

No items found.
React Galaxy City
Get our newsletter

By subscribing to the newsletter, you give us consent to use your email address to deliver curated content. We will process your email address until you unsubscribe or otherwise object to the processing of your personal data for marketing purposes. You can unsubscribe or exercise other privacy rights at any time. For details, visit our Privacy Policy.

Callstack astronaut
Download our ebook

I agree to receive electronic communications By checking any of the boxes, you give us consent to use your email address for our direct marketing purposes, including the latest tech & biz updates. We will process your email address and names (if you have entered them into the above form) until you withdraw your consent to the processing of your names, or unsubscribe, or otherwise object to the processing of your personal data for marketing purposes. You can unsubscribe or exercise other privacy rights at any time. For details, visit our Privacy Policy.

By pressing the “Download” button, you give us consent to use your email address to send you a copy of the Ultimate Guide to React Native Optimization.