Measuring and Improving React Native Performance

Rate this content
Bookmark

The web has several tools to measure performance, but what about us, React Native devs? Have you ever added some optimization in your code (like a memo here and there) and wondered if it had a real impact on performance?

Presenting a Flipper plugin to measure React Native performance, with a concrete case of how we used it to bring our app performance to the top quality.

Bonus: a deep dive into React DevTools flame charts!

19 min
21 Jun, 2022

Video Summary and Transcription

This Talk discusses mobile app performance and provides tips for performance analysis. It includes a performance test on the TF1 news app and demonstrates how to analyze JS performance with React DevTools. The Talk also explores optimizing rendering in React components, such as FlatList and nested lists with carousels. It concludes with strategies for optimizing the virtualized list and carousel to achieve better performance.

Available in Español

1. Introduction to Mobile App Performance

Short description:

Hi, everyone. I'm Alex, a tech lead at BAM. Let's talk about mobile app performance. Good performance means running at 60fps, drawing 60 images per second. In React Native apps, the JS thread can cause unresponsiveness. Use the React Native Performance Monitor and Flipper plugin for monitoring. Test on lower-end devices and make performance measures deterministic. Automate behavior to reproduce conditions.

Hi, everyone. Super excited to be talking to you at React Summit. I'm Alex. I'm a tech lead at BAM. We develop mobile apps in Kotlin, Flutter, and of course, React Native, and I just love the subject of mobile apps' performance, so let's dive in.

What does it mean for a mobile app to have good performance? Well, according to this video from Google, which will be available in slides, your apps should be able to run at 60fps, 60 frames per second. This means that your app should be able to draw 60 images per second when you scroll down, for example, to give an impression of smoothness to the user. It's basically like a movie. It's animated pictures, basically.

The question is what about React Native apps? Well, this also applies to React Native apps, but there is an added complexity, the JS thread. Because most of your logic will run on the JS, most of your business logic probably resides on the JS side of things, you need to make sure that it's not too busy. For example, here I have this app with a Click Me button. When I click it, the state updates. I've been clicked one, two, three times, but Kill.js is too expensive, so when I click it, the JS side of things is blocked. So, even if I click Click Me a lot of times after clicking Kill.js, nothing happens, and it's not before Kill.js has finished that the JS actually becomes responsive again, and you can see it update four to 12 times like that. So, it's very important to take a look at the JS side of things as well, because your app could be running natively at 60 FPS but be completely unresponsive. This is why React Native offers this view, the React Native Performance Monitor, displaying Ui and JS FPS, and this is why we created this Flipper plugin, to be able to display it in a graph. Also, as an added bonus, it gives you a nice score for you to be able to run performance benchmarks. But, chances are, this score could depend on a lot of factors, actually, so let me give you some general tips on performance measures.

The first one is this. You should test on a lower-end device. I mean, if you only test on a high-end device, chances are that you will miss most of the issues that your users are actually could be having. An iPhone 13 can actually run JavaScript or some calculation 10 times as fast as a Samsung Galaxy A21s, so, well, you know what to do. You should definitely test on a lower-end Android device. Second tip is this. You should make your measures as deterministic as possible. Performance measures are hardly deterministic, so you can make several iterations and average the result. You can also make sure to keep the same conditions as much as possible for every measure, for example, network, the data you were loading, et cetera. And, I mean, if you want to reproduce the same conditions, it's ideal to be able to automate the behavior you want to test. And for that, you don't necessarily need an end-to-end testing framework.

2. Tips for Performance Analysis

Short description:

To ensure accurate performance measures on the JS side, disable JSDev. Use React Devtools and JS Flame Graph for analysis. On the UI side, utilize Android Studio, Systrace Profiler, or Xcode Instrument.

You can just use ADB on Android, for example. Here, with ADB Shell Input Swipe, you can actually trigger a scroll on your app. Third tip is this. Disable JSDev if you want to have true performance measures on the JS side, you should disable this, because you might encounter an issue that you won't actually see in production. And the fourth tip is, well, you should find some issues when measuring. Just use the best analysis tools. On the JsThread, that would be React Devtools, or even running a JS Flame Graph with the Hermes profiler. And on the UI side, use native tools like Android Studio, Systrace Profiler, or Xcode Instrument on iOS.

3. Performance Test on TF1 News App

Short description:

TF1 news app performance test on a low-end device. Goal: JSFPS above 0, UIFPS at 60. Used Samsung J3 2017. Measured for 10 seconds, reloaded with JSDev mode disabled. Plugin score: 40/100. JS unresponsive for 4 seconds. UI thread okay.

All right, let's dive in on a concrete example now. TF1 is a TV news channel and we're building their news app, and we wanted to make sure that performance was on par with our quality standards. So we checked the home feed and our goal was that when we scroll down, basically on a low-end device, we should have JSFPS always above 0 to keep the Js side of things kind of responsive, and the UIFPS always to 60 to be able to be very smooth.

So following the four essential tips that I talked about before, basically we use a Samsung J3 from 2017, it's our favorite low-end device, it's pretty low end. We set up measuring for 10 seconds, arbitrary, but it's just important to keep the same time for every measure so you're able to compare. We reload with JSDev mode disabled. We wait for the feed to be loaded, and we hit Start Measuring under the Flipper plugin. We run ADB shell and PutsWide, blah, blah, blah, and we wait until the end of the 10 seconds, and then we reproduce this 5 times just to be able to see if we get a similar result and average it. And the result was this. The plugin was giving us a score of 40 out of 100, JS was dead at zero for about 4 seconds, UI thread was kind of okay. But basically, this means that the app was actually not responsive for about 4 seconds, which, needless to say, was completely unacceptable.

4. Analyzing JS Performance with React DevTools

Short description:

To analyze the JS performance, use React DevTools in Flippr. Click the button on the top right, check the box for live profiling, and start recording. Reproduce the desired actions, stop recording, and review the results.

Since the issue was mostly on the JS side of things, we ran two React DevTools, and it's available out of the box in Flippr, which is nice. First thing you want to do is click on this button on the top right, and there you can check the box, record why each component rendered live profiling. This is a neat option to activate. We will see why pretty soon. Then just click the blue button here to start recording. Do some stuff, so in our case that would be reproducing the exact same scrolling with adb shell input swipe blah blah blah. And then we hit the button Stop Recording, and it gives us a result kind of like this.

5. Analyzing React Component Hierarchy and Rendering

Short description:

The hierarchy of React components shows the rendering status of each component. The virtualized list is the main component, with several child components called cell renderers. Each component has self time and total time metrics, indicating the time taken to render without and with children, respectively. In this case, the virtualized list takes 3 seconds to render, including all list items.

All right. First thing you want to do is check this. This is the list of comments. These are the phases where React actually applies any changes. And so usually when you look at performance issues you want to find the most expansive comments in terms of how much time it took. And in our case this would be the 11th one. It's the biggest bar here. So let's click on it, and this is what it gives us.

Pretty. There's a lot of colors here, but basically this is the hierarchy of your React component. In grey here, you will see components that are actually not rendering, but in our case the red screen and some other colors is rendering. So if we zoom in a little bit on this part for example, you will see the hierarchy of components starting with flatlist here in grey. So not rendering. So our home feed was implemented with flatlist so this is it. And it's an internal implementation in the React Native code. Its child is called a virtualized list. So here it is. Then we have context.consumer, its child is virtualized list.contactProvider etc, all the way down to context.provider, just a list of children basically. And this guy has several children called cell renderers. This is basically what is wrapping the stuff you pass in your render items. So essentially the cell renderer are our list items of flatlist. You will notice also that on every component you have two metrics. You have this one on the left side, for example here 4.9 milliseconds on our virtualized list. It's the self time, it's the time the component took to render without any of its children. And the right one is the total time, it's the total time that the component took to render including all of its children.

So here basically we have a virtualized list taking 3 seconds to render. And we can see that basically all of the list items are actually also rendering. So our list is rendering, all of the items in our list are rendering, and this takes a total of 3 seconds. Which is kind of crazy. I mean remember when we talked about the JS being locked for about 3 or 4 seconds? This totally explains it, we have 3 seconds for that.

6. Optimizing FlatList Rendering

Short description:

Differentiate between initial rendering and re-rendering. The FlatList is a virtualized list that renders a subset of items. Memoize list items to prevent unnecessary re-renders. Gray items are memoized and no longer re-render. Green items are part of the internal implementation and cannot be prevented from rendering. Performance score improved from 40 to 52, with only 3 seconds of unresponsiveness.

So what should we do? Well, the first thing that you want to do is differentiate between initial rendering, and when you hover a component, since we activated this new option earlier, you will see something like that. This is the first time the component rendered. Or are those components actually re-rendering? They have been initially mounted, but they're rendering a second time or another time. And in this case you will see something like this, a virtualized list, it's a state change, a last change, which we don't really know about because it's the internal implementation of FlatList.

In our case, actually, all of the components here were re-rendering because, remember, I said that we had loaded our feed previously so it was rendering already some items in the feed, so when we scroll down, basically it's re-rendering even the top items in the feed. And this is crazy, right? They don't need to be rendered. So what is going on? Well, the first iteration we took to fix was this. And everyone should know about this. So the FlatList is a virtualized list and virtualization works kind of like this. You have a long list of elements, for example, I don't know, 10,000, but of course, for performance reasons, you're not going to be able to display them all at the same time. You use a virtualized list to display only what the user sees and some items above and some items below to ensure smooth scrolling. It's important to notice that in React Native, this is the FlatList property called window size and in React Native, actually, it's going to render 10 screens' worth of items above the current viewport and 10 screens below it. So it actually renders a lot of items. It's a good thing to keep in mind. But so how does this work? Well, basically, the FlatList is keeping a state in its internal implementation of the first element to render and the last element to render. So when we scroll down, well, those change. And so this triggers a re-render of the virtualized list, which is normal. So basically, this means that render item, what you pass in your render item is called onScroll. This is by design, which means you should absolutely follow the best practices defined in the React Native documentation about FlatList, and you should memoist all of your list items. And that's what we did, and we went from this to this.

So the big difference that you should notice is this. Basically, we have a lot of gray here. The items that we memoist are now not re-rendering, and this is what we want. We still have some green stuff above it. This is basically the internal implementation of virtualized lists, state changes, etc. So we can't really prevent it from rendering, and what is really expensive here is the children, and we still have some green stuff here. We'll get to that in a minute, because first, we already have some less green stuff, we have some more stuff, I mean, we have less stuff rendering, so, let's just check our performance score. Let's check our measures, just like before on the Flipper plugin, and this is what it gives us now. We went from 40 to 52, Chase is only dead for like 3 seconds, and the UI thread is still around 60. So ok, it's not fantastic, but already, just with a simple memo, we made a very good improvement for performance.

7. Exploring Nested Lists and Carousel Rendering

Short description:

Let's dive deeper into nested lists. The green items at the bottom are not virtualized lists, but horizontal carousels implemented with React Native Snap carousels. These carousels, along with the parent list, re-render when scrolling due to context change. To optimize performance, we memoized the slide items, resulting in more gray at the bottom.

But let's go deeper. Second iteration, I titled it the Joy of Nested Lists, and you will understand why pretty soon, because, yeah, remember those green stuff at the bottom? Let's zoom in to see what this is. So we have our cell render at the top, so this is wrapping what we pass in our render item, then we have the gray stuff, because we memoized it, but we have here something that is still rendering in green. And it's not a virtualized list. This is because we have horizontal carousels implemented with React Native Snap carousels, which itself uses a flat list. So essentially here you see our seven carousels that are displayed on our home feed, re-rendering. But why are they re-rendering when we scroll down? Well, remember the state, the window state we talked about before for the parent list? It passes it as context to the nested child list. So we can see here that the virtualized lists are re-rendering because of context change. And this means that when we scroll the parent list, nested lists, even horizontal, the render item will also be called. So we have to memoize everything, and in our case that would be the slide items. And so that's what we did, and we went from this to this. Might be hard to see, but now we have a lot more gray at the bottom.

8. Optimizing Carousel Rendering

Short description:

We observed new list items appearing when scrolling down, which is expected. However, excessive re-rendering resulted in minimal improvement in performance. Upon further investigation, we discovered that the React Native Snap carousel's loop prop was causing additional slides to render. By deactivating this prop, we reduced the number of slides and significantly improved performance.

So oh, we have some stuff appearing on the right side. We can check what this is. Okay, there are actually re-rendering for the first time, which means that this is probably new list items appearing when we scroll down. This is okay.

Let's check first excessive re-rendering. So now we have this. Lots of gray at the bottom. Let's check more score. And we went from 52 to like 54. Pretty much the same, basically. Not super good improvement. So we need to go deeper.

All right, let's check a bit more those carousels. If we zoom in on this guy here, we can see that basically it's a virtualized list re-rendering. Okay with our carousel, we've worked 10 slides. Wait, 10 slides? No, actually our carousels only have 4 slides. Oh, but that's right. We added the loop prop on the React Native Snap carousel, which means that it will actually add 3 slides on each slide of your carousel at the beginning and at the end to achieve the infinite loop effect. The brightest among you will have noticed that this checks, this equals 10. So we decided to just deactivate this prop for performance reasons.

So now we went from this to this. We can see that indeed we only have 4 slides rendering. So let's check our score. Our score is much better. We went to 70. C, GS, is only that for one second. Great, great. Still OK. All right. We're getting there, but still not perfect.

9. Optimizing Virtualized List and Carousel

Short description:

Let's dive deeper into the virtualized list and identify the expensive animated component. By avoiding nesting virtualized lists and recoding the carousel with a scroll view and paging enable prop, we significantly improve performance. Our score is now 90, with no zero JS FPS and a consistently responsive UI thread. Try measuring your app's performance, identifying and fixing issues, and tracking score improvements. Thank you for watching!

So let's go even deeper. Right, let's click on this first carousel, this virtualized list. We can see all of the items. If we zoom in on the first one, this is what we see. We see our virtualized list here. Still taking 75 milliseconds to not do anything, basically. But we noticed this guy, animated component being in orange. React DevTools will print out, in a different color, like orange here, items which have a very big self-time. So they're very expensive to render, compared to their children. And so this guy here is expensive to render.

But where is it coming from? So the top here is from the flatlist code, from virtualized list to cell render. This gray stuff is our slide. It's memoized. So here, this is coming from React Native's natural cell. Actually, the animated component is here to have some cool, nice transition. But not only is it quite expensive to render, it's not even memoized, and it should be. So we could patch React Native's natural cell. Or at this point, we kinda realize that nesting virtualized list is tricky. So the question is, can we avoid it? And the answer is yes, because remember what I said about virtualization by default in React Native displaying 21 screens worth of items. In our case, we only have four items, so virtualization here for carousel brings no benefit whatsoever. So we decided to recode it ourselves with a scroll view and paging enable prop, basically. And now we have only gray here when we scroll down. Nothing is re-rendering excessively.

So let's check our score. Now we're at 90, and the JS is never at zero, and the UI thread is always okay. Woo! We've done it. So I encourage you to try it out, measure your app's performance, take a low-end Android device, find areas with the plugin with zero FPS on JS side or below 60, analyze those issues and fix them, and check back your score improvement. That's it. Thank you guys for watching, hope you enjoyed the talk. If you have any questions, feel free to ping me on Twitter or follow me, I tweet mostly about React Native Performance.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

JSNation 2023JSNation 2023
29 min
Modern Web Debugging
Top Content
Few developers enjoy debugging, and debugging can be complex for modern web apps because of the multiple frameworks, languages, and libraries used. But, developer tools have come a long way in making the process easier. In this talk, Jecelyn will dig into the modern state of debugging, improvements in DevTools, and how you can use them to reliably debug your apps.
JSNation 2022JSNation 2022
21 min
The Future of Performance Tooling
Top Content
Our understanding of performance & user-experience has heavily evolved over the years. Web Developer Tooling needs to similarly evolve to make sure it is user-centric, actionable and contextual where modern experiences are concerned. In this talk, Addy will walk you through Chrome and others have been thinking about this problem and what updates they've been making to performance tools to lower the friction for building great experiences on the web.
DevOps.js Conf 2022DevOps.js Conf 2022
31 min
pnpm – a Fast, Disk Space Efficient Package Manager for JavaScript
You will learn about one of the most popular package managers for JavaScript and its advantages over npm and Yarn.A brief history of JavaScript package managersThe isolated node_modules structure created pnpmWhat makes pnpm so fastWhat makes pnpm disk space efficientMonorepo supportManaging Node.js versions with pnpm
React Advanced Conference 2023React Advanced Conference 2023
29 min
Raising the Bar: Our Journey Making React Native a Preferred Choice
At Microsoft, we're committed to providing our teams with the best tools and technologies to build high-quality mobile applications. React Native has long been a preferred choice for its high performance and great user experience, but getting stakeholders on board can be a challenge. In this talk, we will share our journey of making React Native a preferred choice for stakeholders who prioritize ease of integration and developer experience. We'll discuss the specific strategies we used to achieve our goal and the results we achieved.
React Finland 2021React Finland 2021
27 min
Opensource Documentation—Tales from React and React Native
Documentation is often your community's first point of contact with your project and their daily companion at work. So why is documentation the last thing that gets done, and how can we do it better? This talk shares how important documentation is for React and React Native and how you can invest in or contribute to making your favourite project's docs to build a thriving community
React Day Berlin 2023React Day Berlin 2023
29 min
Bringing React Server Components to React Native
Top Content
React Server Components are new topic in community, bunch of frameworks are implementing them, people are discussing around this topic. But what if we could use React Server Components in React Native? And bring all optimisation features that RSC allows to mobile apps? In this talk I would present what we are able to do with RSC in React Native!

Workshops on related topic

React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Top Content
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
React Advanced Conference 2022React Advanced Conference 2022
81 min
Introducing FlashList: Let's build a performant React Native list all together
Top Content
WorkshopFree
In this workshop you’ll learn why we created FlashList at Shopify and how you can use it in your code today. We will show you how to take a list that is not performant in FlatList and make it performant using FlashList with minimum effort. We will use tools like Flipper, our own benchmarking code, and teach you how the FlashList API can cover more complex use cases and still keep a top-notch performance.You will know:- Quick presentation about what FlashList, why we built, etc.- Migrating from FlatList to FlashList- Teaching how to write a performant list- Utilizing the tools provided by FlashList library (mainly the useBenchmark hook)- Using the Flipper plugins (flame graph, our lists profiler, UI & JS FPS profiler, etc.)- Optimizing performance of FlashList by using more advanced props like `getType`- 5-6 sample tasks where we’ll uncover and fix issues together- Q&A with Shopify team
React Summit 2022React Summit 2022
117 min
Detox 101: How to write stable end-to-end tests for your React Native application
Top Content
WorkshopFree
Compared to unit testing, end-to-end testing aims to interact with your application just like a real user. And as we all know it can be pretty challenging. Especially when we talk about Mobile applications.
Tests rely on many conditions and are considered to be slow and flaky. On the other hand - end-to-end tests can give the greatest confidence that your app is working. And if done right - can become an amazing tool for boosting developer velocity.
Detox is a gray-box end-to-end testing framework for mobile apps. Developed by Wix to solve the problem of slowness and flakiness and used by React Native itself as its E2E testing tool.
Join me on this workshop to learn how to make your mobile end-to-end tests with Detox rock.
Prerequisites- iOS/Android: MacOS Catalina or newer- Android only: Linux- Install before the workshop
React Summit Remote Edition 2021React Summit Remote Edition 2021
60 min
How to Build an Interactive “Wheel of Fortune” Animation with React Native
Top Content
Workshop
- Intro - Cleo & our mission- What we want to build, how it fits into our product & purpose, run through designs- Getting started with environment set up & “hello world”- Intro to React Native Animation- Step 1: Spinning the wheel on a button press- Step 2: Dragging the wheel to give it velocity- Step 3: Adding friction to the wheel to slow it down- Step 4 (stretch): Adding haptics for an immersive feel
React Advanced Conference 2023React Advanced Conference 2023
159 min
Effective Detox Testing
Workshop
So you’ve gotten Detox set up to test your React Native application. Good work! But you aren’t done yet: there are still a lot of questions you need to answer. How many tests do you write? When and where do you run them? How do you ensure there is test data available? What do you do about parts of your app that use mobile APIs that are difficult to automate? You could sink a lot of effort into these things—is the payoff worth it?
In this three-hour workshop we’ll address these questions by discussing how to integrate Detox into your development workflow. You’ll walk away with the skills and information you need to make Detox testing a natural and productive part of day-to-day development.
Table of contents:
- Deciding what to test with Detox vs React Native Testing Library vs manual testing- Setting up a fake API layer for testing- Getting Detox running on CI on GitHub Actions for free- Deciding how much of your app to test with Detox: a sliding scale- Fitting Detox into you local development workflow
Prerequisites
- Familiarity with building applications with React Native- Basic experience with Detox- Machine setup: a working React Native CLI development environment including either Xcode or Android Studio
React Summit 2023React Summit 2023
88 min
Deploying React Native Apps in the Cloud
WorkshopFree
Deploying React Native apps manually on a local machine can be complex. The differences between Android and iOS require developers to use specific tools and processes for each platform, including hardware requirements for iOS. Manual deployments also make it difficult to manage signing credentials, environment configurations, track releases, and to collaborate as a team.
Appflow is the cloud mobile DevOps platform built by Ionic. Using a service like Appflow to build React Native apps not only provides access to powerful computing resources, it can simplify the deployment process by providing a centralized environment for managing and distributing your app to multiple platforms. This can save time and resources, enable collaboration, as well as improve the overall reliability and scalability of an app.
In this workshop, you’ll deploy a React Native application for delivery to Android and iOS test devices using Appflow. You’ll also learn the steps for publishing to Google Play and Apple App Stores. No previous experience with deploying native applications is required, and you’ll come away with a deeper understanding of the mobile deployment process and best practices for how to use a cloud mobile DevOps platform to ship quickly at scale.