House
/
Blog
/
Optimizing a React Native FlatList With Many Child Components
React Native

Optimizing a React Native FlatList With Many Child Components

This Slack transcription is a deep dive into how to optimize the initial render of React Native's Flat List when it is the parent of many other smart child components.

Optimizing a React Native FlatList With Many Child Components

A few weeks ago there was a fantastic conversation that happened in our g2i-collective Slack channel around a problem one of our members (Darius Mandres) was having. He was trying to optimize a FlatList in his app that rendered a fairly large number of child components. He turned to the collective for help and was assisted by several other members. We think this discussion is a great example of the value of the G2i collective and could be helpful to other developers facing similar problems. Special thanks to all those who helped Darius out:

The discussion has been replicated in as close to its original form as possible. Some changes were made to protect the IP of Darius' application.

Darius Mandres

Hey, I have a RN performance question to ask: my problem is a slow initial render. Subsequent renders are fine and optimised with `useCallback` and `useMemo`, etc. I have a social media app with a complex post feed where each post card renders multiple sub components, that each render other sub components as well. There’s no way around this. What I find myself having is that even on 10 items initially rendered in a `FlatList`, I have a noticeable ~500ms delay on the first render. This causes my app to feel slow and I’m trying to fix it. The problem doesn’t seem to be the UI components as they’re all optimised and re-render only when they should. It might be the functions I create in there? I’m not sure. For reference, my post card component and its sub components collectively render about ~30 components each including styled components, etc. So 10 of them would mean ~300 components, probably more actually. I have 2 questions:

  • I realise it’s quite a few components. Is the delay to be expected? If so, how would you suggest keeping the app complexity while reducing the delay?
  • How do I figure out what is causing the delay? I need to find out if any function is the culprit, or a series of functions, or anything else?

Andrei Calazans

Few things. Profiling is a must. Use safari when profiling iOS simulator to be sure you are using the same JS Executor (JSCore). Enable developer mode in Safari and you should be able to attach to the JS instance - see screenshot. (https://reactnative.dev/docs/debugging#safari-developer-tools) By profiling you will be able to see where you are spending more time. Besides profiling you can also benchmark the cost of each rendering/construct of components isolated by just timers. Only make changes once you have actual numbers. Other than that FlatList does have a few props you can play with like windowSize and initialNumToRender in order to improve initial load.

John A Kallergis

Of course what Andrei mentioned is crucial. Profiling and a proper debugger is key. In that regard, using profiling and benchmarks, this is how I would approach it:

  1. create the same exact UI but with hardcoded data, and without any lifecycles or contexts/states changing. Just plain components passing static props around - as little re-renders as possible. Ideally just one render (after all, we currently only care about that initial render).  This way if the delay remains the same, then I would eliminate the problem being the amount of components. If the delay is gone, then it might be the way you structure global state management, the lifecycle of higher level components, or even the amount of components.
  2. Then I’d put the dynamic data in (still no lifecycles in the rest of the components, except the one who triggers the fetching) and see if loading them slows things down. If that’s the case I would check for any costly transforms performed on the data etc
  3. If adding the dynamic data doesn’t slow things app, then I’d start adding other lifecycle stuff in, one by one paying close attention on the performance.

But these also depend on how the state management is structured.

Garrett McCullough

I once had a performance issue and through profiling, I found it was due to the mounting of so many nested components that were imported from a library. If you end up with something like that, then you may have to get into some of the perceived performance tricks like showing a loading screen, loading the components in the background and then switching over to the components once they render

Darius Mandres

@Andrei I had for now been profiling using chrome and mostly using the react dev tools profiler which just shows re-renders and time for renders in chrome. I’ll give it a shot on safari and try a more in-depth analysis. Thanks for the tip!

Darius Mandres

@John A. Kallergis that’s an option that I am definitely keeping in my arsenal, only I’d use it as a last resort as I really don’t want to go through the entire component tree to make a stripped down version of it without any data. Having that many components will be a nightmare but if I must do it, then I don’t have a choice I guess. To give a little context, for this part of the app alone I’m only fetching data pre the first render. After that, no further data is loaded. I just have a bunch of sub-components who render separate parts of the post card, and each has its own set of functions and derived data, and styled components. I’m curious when you said that the issue could be too many components. Is there such a thing in react? This is the largest app that I’ve built and I’ve never had to run into such bottlenecks before. Again, it does depend on how they are structured like you said, but I had the good mindset to always adopt the best practices to the T when I learnt and use React, so I’m unsure of where I’ve gone wrong

John A Kallergis

I’ve build huge RN projects and so far I haven’t seen the amount of components being the problem. One of them was a HUGE iPad app with two separate navigators side by side at the same time, three lists on the same view with FlatLists and SectionLists and what not. Over 400 different components in total in a single project. Never had an issue with the amount of components being rendered so far. At least not when it comes to the actual number. But maybe when you take their content into consideration as well things change a bit. You could have thousands of simple View components rendered and it might be just fine.  However that wouldn’t be the same if you had a handful others, each rendering huge vector paths. Not to mention animating vector paths. Boom CPU/GPU gone

Darius Mandres

I see, interesting, thanks for sharing! I’ve done some profiling yesterday and haven’t found a single culprit component. Also, I’ve taken an easier approach to just remove all sub-components from the post card and profile it by adding them one by one. I’ve found that 10 post card views take about 50ms to render when completely empty, and with the full sub components rendered, it goes up to 270ms. Every sub-component brings a slight increase to the total time, so I’m now investigating what exactly is causing it.

Darius Mandres

Here’s part 2 to my performance post from yesterday, with updates, and screenshots. Hopefully this can bring value to all of us. I wasn’t able to find a single culprit component, nor a single culprit function. All of my profiling thus far leads to my performance being a result of many sub-components. As you can see in the screenshot below, the total time it took for 10 PostCards to render was `207.8ms`. The screenshot zooms in on one PostCard, which took `30ms` to render. Looking at the profiling, I have quite a few contexts, providers, styled components that all add up to the render time. I am not sure what to do here. I mean in terms of functionality, this isn’t really that complex, yet it is slow even rendering a few PostCards. I have never gone this far to optimise a RN app, so I’m not quite sure of the numbers here and whether or not they are expected or too high. Anyone have any thoughts or comments? I’m in Europe so I’ll be around most of the day to discuss/figure this out!

RN profiler results
RN Profiler results

John A Kallergis

I’d try this next:

  1. find the most complex component in terms of styling. By that I mean any component that renders a lot of styled components that also use a lot of style properties.
  2. create a separate project and render only that component (no children custom components - only styled components and their styles)
  3. Measure performance
  4. create a new version of the component that doesn’t use styled-components at all, but instead it uses plain old `StyleSheet` styles defined outside of the component
  5. Measure and compare performance

Not trying to throw shade on styled-components here. I really love what they offer. But it is my personal opinion and experience that there’s nothing faster in styling than registering a style using `StyleSheet` and using that (maybe styled-components do the same under the hood, but I would check, just to make sure)

Darius Mandres

Do you really think they’d be the culprits? Wouldn’t it show up on the profiler though? I’m curious as to why the render times take Xms but I can’t pin point any render above 0.1ms - 1.3ms in that screen

John A Kallergis

hmmm fair

Darius Mandres

Here’s something interesting. I have a FlatList component rendering all of this, that one seems to render a lot longer than my post card components (which in this screenshot are stripped of everything but their container styled component). Here the Post Cards account for only half the render time it seems

RN Flatlist profiler
RN FlatList Profiler

Andrei Calazans

@Darius can you share the FlatList portion of the code? Can you also share a screenshot to the actual UI so we can better understand the requirements?

John A Kallergis

Then maybe the list calculating the layout is your bottleneck? Does it get better if you implement getItemLayout?

Andrei Calazans

I see a lot of Context wrappers in the flame graphs, what are those for? Are you the one injecting them?

Exactly. He could get better numbers with getItemLayout + windowSize + initialNumToRender + maxToRenderPerBatch https://reactnative.dev/docs/optimizing-flatlist-configuration

Darius Mandres

Sure @Andrei! Included a pic of the UI, a pic of the FlatList, and a pic of the PostCard component. Pics are cropped but it shows the important parts. I’ve also implemented initialNumToRender & maxRenderPerBatch and that helps, although it splits the render times at the cost of blank screens, which isn’t the best but if it’s the only option, I can live with it. Regarding the context wrappers, they come from redux as most components are connected, or from custom context hooks that I use to access certain things like modal data, and forms (Actual image replaced with an example wireframe. Code samples simplified for example purposes.)

example wireframe
Example Wireframe
const [verticalOffset, setVerticalOffset] = useState(0) const [scrollingUp, setScrollingUp] = useState(true) const [scrollingDown, setScrollingDown] = useState(false) const onScroll = useCallback( (e: NativeSyntheticEvent) => { if(e.nativeEvent.contentOffset.y < verticalOffset - 30) { setScrollingUp(true) setScrollingDown(false) } else if (e.nativeEvent.contentOffset.y > verticalOffset + 30 { setScrollingUp(false) setScrollingDown(true) } setVerticalOffset(e.nativeEvent.contentOffset.y) }, [verticalOffset] ); useEffect(() => { if(scrollingUp && onScrollUp) onScrollUp(); if(scrollingDown && onScrollDown) onScrollDown(); }, [scrollingUp, scrollingDown]) if(showSkeleton && skeletonComponent) { return ( <> {listHeaderComponent} {skeletonComponent} ) } return ( : null} ListEmptyComponent={listEmptyComponent} showsVerticalScrollIndicator={false} initialNumToRender={initialNumToRender} maxToRenderPerBatch={initialNumToRender} numColumns={numColumns} inverted={inverted} contentContainerStyle={style} style={styles.flatList} onViewableItemsChanged={onViewableItemsChanged} onScroll={onScroll} /> )}
return ( {postMedia && ( )} {mediaIsUploading && ( )} {mediaIsProcessing && ( {text} )} )

Andrei Calazans

So the post is pretty much full screen?

If it is then try windowSize={3} initialNumToRender={3} maxRenderPerBatch={1} you might need to tweek this to 3. updateCellsBatchingPeriod={10} Test with the period as well - you might not need this at all. The more you render per batch the longer the period

and getItemLayout is a must too

It will reduce the initial load time a bit since it skips measuring

And at th end it will be a balance between faster initial load versus blank spaces

Darius Mandres

if the post has media, it will take up most of the screen and the next one will be partially visible, if it has no media, then 3-4 can fit on one screen depending on the device

so is your conclusion that nothing can be done to speed up the render of 1 single component and that the only solution is to get creative with how many are rendered and when?

Andrei Calazans

So the 30ms time it takes to render a single component is reasonable. While sure you can optimize it further, but not without changing the structure of your PostCards significantly — no context wrappers, no styled-components, shallow component tree, etc. Thus, the easiest win for initial load is rendering less items in the list. Another thing you can, which is very creative and a bit ugly, is to wrap your components with a DelayedRender HOC that takes  a comp and a delay and only renders the comp after the given delay. Something like:

renderAfterDelay(Comp: React.Element, delay: number); // will return an empty View with the same height of your PostCard, then return the actual passed Comp after the delay is completed. renderItem={({ item, index}) => renderAfterDelay(, DELAY * index)}

But I don’t recommend the above. It is though up to you

Darius Mandres

Interesting, do you think that is a problem with RN? If you look at Twitter or Facebook or even Instagram, they have similar complexity and have really great performance, definitely much less than 30ms per item rendered

Andrei Calazans

I won’t coin it as a problem, but rather a trade-off. The basics of React Native is this, the render goes depth first in the tree (your deepest component) and walks up notifying the native layer with the UIManager telling it what views to create or update plus what styles to apply. You can see how much communication is happening by logging the following: Add this to your root file

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'; // Disable warning box on development mode console.disableYellowBox = true; // Spy debugging if (__DEV__) { const logSpy = (info) => { const fromTo = info.type === 0 ? 'TO JS: ' : 'TO NATIVE LAYER: '; const methodSignature = info.module + '.' + info.method + '(' + JSON.stringify(info.args) + ')'; console.log('spy: ', fromTo + methodSignature); }; MessageQueue.spy(logSpy); }

Technically the less work the faster of course. You can likely reduce the 30ms down significantly if you refactor your components. Idk how much you can do so, try to get a baseline by measuring how long it takes to render a View with somes styles a simple Text centered in it. Make sure to measure this inside the FlatList since it wraps every element with more stuff.

Darius Mandres

Yeah it’s a hard one to find, most of my components take around 5ms to render, which isn’t much but when you combine them into larger components, like the post card, it adds up quickly. I’m not sure how much I can refactor this as it is purely based on what is being rendered, and no function is causing issues. I don’t want/can’t change what is displayed in which screens, so I need to see another way. I noticed this in other RN apps too, so I’m unsure of what the best approach is. Now that I understand it however, I am positive that there is an approach that will solve it.

I’m also surprised that I cannot find anything about specifically slow renders online. Just about re-renders. I wonder if people just don’t notice or care about the slight delays in rendering in RN

Andrei Calazans

Well, this is isn’t a common complaint. 30ms is not that bad.

I’m not sure how much I can refactor this as it is purely based on what is being rendered, and no function is causing issues.

You can improve it by refactoring as I said:

  • Remove styled components and use StyleSheet instead
  • Remove Context API wrappers in list items
  • Try to normalize the tree structure by removing deeply nested elements.

and what platform is this? (iOS or Android?) You could improve initial render times by using Hermes

Darius Mandres

I’m building a 3x cross-platform app for web, ios, and android so I’m limited to using things that are common between the 3. For example, I need to use styled components as it is the only way to share styles between web and native without re-writing them. The priority is to make development tightly coupled between all 3, and reduce the friction from working on one platform vs the other. At first it was just an idea, but I’ve since built a pretty nice structure that allows practically all of the code to be shared, while using pure react and pure react native respectively. All this to say that yes, I can re-factor and make optimisations, but perhaps not in the usual ways

I also have this thing for wanting it to be very smooth and efficient, so that’s why I complain about 30ms

and to clarify the above, I am planning to use hermes on android and make other optimizations on web and ios, specific to each platform. I was referring more to the shared parts, which I want to get as efficient as possible and then work on platform specific stuff

Andrei Calazans

Fair enough. Let us know what you find out and how you overcome this, I’m sure you will

Bruno Lemos

I had this performance issue when building https://github.com/devhubapp/devhub, which is a cross-platform app too (ios/android and web). The solution involved many changes and rewrites, but here’s a small summary:

  1. Simplify your item component. Instead of rendering multiple nested components per list item, render a single one that is very simple and dump, that just render the received props. Really, make it be a very dumb component.
  2. Do not do any calculation or any task inside the list item, including accessing context, hooks and any thing other that a basic pure function component
  3. You should be able to calculate the height of each list item based on their props BEFORE rendering, and pass these heights to flatlist getItemLayout prop (this makes a HUGE difference)
  4. Make sure the list and the items are properly memoized and only rerender when necessary
  5. Drop styled components, use StyleSheet.create instead, at least in the list items (SC does a lot of things that adds up to performance issues like accessing context, parsing template literals, converting css strings to react-native objects, etc)
  6. Enable virtualization and experiment with the options to only render the minimal items necessary that are visible on the screen
  7. Use react-window on web (on DevHub I unified the FlatList and react-window's api into a custom list called OneList, here’s the https://github.com/devhubapp/devhub/tree/master/packages/components/src/libs/one-list and https://github.com/devhubapp/devhub/blob/456017cb6051ead34645bbf90d4a70a0bbb74d0b/packages/components/src/components/cards/NotificationCards.tsx#L197-L225
  8. Experiment with concurrent mode on web (not sure if fabric is already usable on mobile)
  9. Use and abuse the Profiler tool, not just the React one, but specially the one built-in on Chrome to find expensive javascript methods (you may be surprised on some simple methods that you have that may be slowing things down! in my case it was some regex, for example)

Darius Mandres

@Bruno oh damn, you’re part of G2i! Had no idea! I’ve been looking at DevHub as a reference example actually, both source code and the app. What I notice however, is that you have the same problem I have. Whenever you navigate to the “Notifications” or the “Home” tabs, there is a significant delay between the finger touch and the page actually being displayed. You don’t have this on other tabs where the rendered output is much smaller, so I’m not sure how about that! Also, regarding making things a dumb component, on DevHub, on those mentioned tabs, the components each open up a github link in a modal, so you got away with making them dumb. However, in my case, I need the PostCard to do multiple things, so I cannot not have any function in there. How would you suggest keeping the same functionality?

Bruno Lemos

I’m building a 3x cross-platform app for web, ios, and android so I’m limited to using things that are common between the 3.

You don’t need to limit yourself. Like I mentioned above, I used react-window on web and flatlist on mobile. You can always write platform-specific code, without any limitation

For example, I need to use styled components as it is the only way to share styles between web and native without re-writing them.

Many css-in-js are cross platform nowadays, but I recommend sticking with StyleSheet for performance reasons

Darius Mandres

Also, I haven’t looked at the styles in DevHub in depth, do you re-use styles? if so what is your approach to it?

Bruno Lemos

Whenever you navigate to the “Notifications” or the “Home” tabs, there is a significant delay between the finger touch and the page actually being displayed.

Yeah I think I eventually messed up performance again after I added more features, swipeable, etc, but follow those principles above that you’ll get an improvement

You may also experiment with some “real native” list components, like Discord did, or try some js alternatives like the one from flipkart

do you re-use styles? if so what is your approach to it?

Yes I just write normal StyleSheet.create like we do on mobile-only apps, react-native-web makes it work on web automatically

Darius Mandres

aha, I use React, not react-native-web for the web portion. I felt more comfortable using native web elements for it, so that’s why styled components

Bruno Lemos

so are you writing <div>?

Darius Mandres

yeah

well, I’m using styled components for it all, but on mobile they render views and web divs

Bruno Lemos

hum ok. you could use only StyleSheet from rnw but not sure if it’s worth the trouble

Darius Mandres

Have you considered Facebook’s approach to how they re-did their web app? Loading the least amount of data only when needed? It’s a little trickier to do on mobile but I’m thinking of delaying as much of the rendering as possible until absolutely needed. Because there is no other way really. I can’t make my components any dumber, since I need functionality there. So I’m trying to think up a way to solve that.

Bruno Lemos

One idea: You need functionality but you don’t need it all at once. You can render a dump component and than lazy render the full-featured component. For example, only render the post image and lazy load the other image-related features like pinch to zoom etc

talking about images, if the image is too big it may affect performance too

Darius Mandres

yup, that’s exactly what I’m talking about :), that way, you get performance and user experience, while retaining all functionality

I’m probably going to have to make some HOCs that take care of rendering a dumb component, and then lazy loading the full one

Bruno Lemos

ah you said “data” I thought about api calls

Darius Mandres

ah no sorry, no data whatsoever, only components. All data is already loaded once list is displayed

Bruno Lemos

Facebook is probably using Fabric on production, which is reported to have a much better performance than what we use by default

Darius Mandres

yeah they definitely are using more performant stuff. Either way, I’m eventually going to write a blog post about it detailing the performance journey of my app and how far I get, so I want to thank you all for the advice so far! It’s been very informative and truly helpful :)

John A Kallergis

nice! remember to share a link when you’re done!

Bruno Lemos

@Darius about the “tabs” component in devhub, since the tab items are dynamic and I didn’t use react-navigation, I made my own basic tab navigation solution and it unmounts the contents and re-render from scratch on change. that’s not ideal. if you use react-navigation v5+ with native integration enabled or some native tab component you should get great results, with instant tab change since the render will be cached

Darius Mandres

@Bruno ah yeah, using react navigation for that will keep them mounted and easily switch between them. However, I think you’d still run into that problem on first navigation to any tab right?

<Break in the conversation>

Darius Mandres

Hey guys, just a little tip if you have a FlatList with many rows of nested components and are having performance problems. I spent quite a lot of time researching this and I’ve come to the conclusion that even with all of the FlatList optimisations that are available out there, the problem is FlatList itself.I’ve gone deep into all optimisation props and still I was getting slow renders as the list was increasing in size. Running profilers kept showing list elements being re-rendered when they shouldn’t, even with strict memo equality checks put in place.Ultimately, to render thousands of components that each nest a few dozen other components each, I had to resort to using RecyclerListView. It’s a pain to setup, and it’s a real pain to get it to work just like FlatList but I’ve done it and the performance is night and day. I was convinced I was doing something wrong with my code when in fact it was RN’s FlatList component all along.Check it out and give it a try if you really have a large app and need this performance. Hopefully this helps someone! https://github.com/Flipkart/recyclerlistview

looking for pre-vetted developers?
Group

Related Articles