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 Text, View or TextInput. 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 TextInput components to register user details and a Touchable component to handle user interaction. This approach is true from the very first component that we create within our application and holds true 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 ScrollView and View 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 FlatList – 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 ScrollView:
to a list based on FlatList.
The difference is significant, isn’t it? In the provided example of 5000 list items, the ScrollView version does not even scroll smoothly.
At the end of the day, FlatList uses ScrollView and View components as well - what’s the deal then?
Well, the key lies in the logic that is abstracted away within the FlatList 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 FlatList may not be enough in some cases. FlatList 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. FlatList 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 FlatList 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 getItemHeight() 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 react-native-text-size 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 FlatList on Android.
FlashList as a successor to FlatList
As already discussed, FlatList dramatically improves the performance of a huge list compared to ScrollView. 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. FlatList 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 FlatList 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 FlatList, 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 FlatList, then what? Luckily for us, the folks at Shopify developed a pretty good drop-in replacement for FlatList, known as FlashList. 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
FlashList recycles the views outside the viewport and re-uses them for other items. If the list has different items, FlashList 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 FlashList. First is estimatedItemSize, the approximate size of the list item. It helps FlashList 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 overrideItemLayout, which is prioritized over estimatedItemSize. 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 FlashList. Remember to turn on release mode for the JS bundle beforehand. FlashList can appear to be slower than FlatList in dev mode. The primary reason is a much smaller and fixed windowSize equivalent. We can leverage FlashList’s built-in call-back functions to measure the blank area onBlankArea and list load time onLoad. 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.
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:
- curl https://get.flashlight.dev | bash
- 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 FlashList.
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.