The title may be a bit assertive, but I think you will agree with me when I list you what we confronted with in our last project with React Native.
We have been experiencing disappointing days and tearful nights for a while, just to have a perfect looking list component. Our priority in this process was of course to test the ScrollView, FlatList, SectionList which are React’s own components. But none of them gave us what we wanted.
First of all, I am very pleased to use React Native in many ways. Getting a cross-platform result with almost the same code is very important in terms of time-saving. However, there are some moments when you feel the lack of using Native libraries. It was one of those things that happened to us. Let’s look at the details of the problem and solutions.
I want to tell you about the problem before we go into more details. We have a long list (memory is a problem) of three columns. This list also has sections and section titles (Texts) of course. As you can imagine, section headings should be full width even though the normal list items has 3 columns. In addition, the height of contents and heading is different. Heading has only a Text component while content has custom components. The whole picture looks like this:
Meeting the requirements
Before I tell you why we couldn’t do all this with React’s list components, let’s list the essential features for our product:
- Column support to have 3 column view
- Sections to show titles
- Memory efficient
What does React Native offer to us are ScrollView, FlatList and SectionList.
- ScrollView: It’s not actually a list component. But the content is customizable and can be used for this purpose. However, ScrollView differs from others in that it renders all child components at once. This means a long render time on a rather long list and an incredible memory usage. This explains why we can never use ScrollView for this job.
- SectionList: That was actually the first thing that came to mind because we had Section feature. But the problem is that there was no multi-column support, and that was a priority for us. We could get data in the format we wanted and get through it somehow, but we didn’t go into it because there was already a FlatList.
- FlatList: It has numColumns property which gives multi-column support. It renders child components lazily just before they appear. Everything looks great except for the lack of section support. We made section headings as separate elements and applied custom rendering. Our web-service is also changed to calculate the columns that would be empty in the three-column list so that the React Native would only focus on rendering. The image was eye-catching and thanks to lazy rendering there wasn’t any slowdown at first rendering. But what is that? If we scroll too quickly, the elements cannot be loaded, either stuck in a scrolling place, or we are witnessing alternating images. This was a really disturbing experience and we did a lot of research. We tried to change the flatlist’s two different parameters, maxToRenderPerBatch and updateCellsBatchingPeriod. This allows you to set how often FlatList will render and how many elements to render at a time. We tried many different combinations of these values. Still, we couldn’t get the user to show what he/she was scrolling without waiting. No problem for us, but people got used to living with speed. Nobody can stand for it. Of course, we could not publish such an application.
Rather than Default Components
And so we exhausted all the options we had. It was clear that we had to go outside the default components. Although the Lazy rendering feature of FlatList and SectionList is useful, they could not meet the demand for fast scrolling in long complex lists.
We had different experiments here and tried to do different things. We would eventually think about writing our own custom native components, just before we discovered a new third-party library. It was react-native-largelist of bolan9999.
I don’t want to go in more detail here, but this library claims to be “Less CPU/Memory usage. Never blanks”. So it was worth trying. There are two different components, WaterfallList and LargeList. LargeList with section support, WaterfallList with column support. In order to use both at the same time, we modified our data a bit and made our own column implementation in LargeList.
The result was still not perfect. This is because the way it works was based on using old elements that were already rendered as a placeholder. As it scrolled, he kept showing the old items and then being refreshed. However, this rate of change was considerably faster than React’s own components.
This time our problem was not performance, but that it was showing the same items over and over again to look like a placeholder. When the user scrolled quickly, he/she always kept seeing contents, but in fact, contents were always the same. This was due to a trick LargeList do in order not to show empty list. Nevertheless, it was quite noticeable on a list of visuals and had to be fixed.
Therefore, we made some adjustments in the source code. We created a new custom component and enabled it to render our content cards. The feature of this component was that it initially shows a blank card as a placeholder and after a certain delay, it displays the original content.
In short, the new system worked as follows:
- First, LargeList creates temporary elements in the new position. These elements do not have the correct data.
- In the meantime, we show the empty placeholder with our new custom component.
- Meanwhile, LargeList finishes detecting and updating the correct data that will arrive there.
- When the waiting period is over, we remove the placeholder and display the content with correct data.
This generates a loading view rather than wrong items, at least from the user’s perspective. We’ve also added a timeout for rendering, inside scroll event of LargeList thus avoided rendering of what’s left behind in case of fast scrolling.
And finally, from the user’s view, there was no problematic look. If the user scrolls quickly, an empty placeholder is displayed for a short time (milliseconds) and the original item is returned. So the user sees a loading image quite briefly and reaches the correct content.
Already there was no problem with slow scrolling, the contents were displayed correctly without any errors.
Although React Native is time-saving in many ways, sometimes the default components can be insufficient for complex operations.
In such cases, third-party libraries come to help, but when they become insufficient as well, you either need to create new custom native components or edit their source code. We tried the second and were satisfied with the result. Unfortunately, React’s own components were not enough for our product’s complex use case.
Although FlatList and SectionList have memory-efficient rendering mechanisms, they may not work properly if you have long complex lists. In such cases, it may be necessary to get out of the possibilities and get help from the LargeList or similar libraries as we do. And even if that doesn’t work out, you might find yourself editing the source code of the libraries on your needs. Get ready for this, if you are not going to create your app natively.
- FlatList by React Native: https://facebook.github.io/react-native/docs/flatlist
- SectionList by React Native: https://facebook.github.io/react-native/docs/sectionlist
- LargeList by bolan9999: https://github.com/bolan9999/react-native-largelist