React Native Performance: Major issues and insights on improving your app’s performance

Shares

React Native Performance: Major issues and insights on improving your app’s performance

Purvak Pathak
in Mobility, Product Engineering
- 23 minutes
react-native-performance

React Native allows you to build Android and iOS application with excellent code shareability and faster shipping cycle. With great ease, comes the performance limitations of React Native.

The React Native performance corresponds to the amount of UI state, Native components, and libraries you’re using in your React Native application.

“Optimization is a core of what software engineer does”

The more tabs, navigation, controls, animations and third-party libraries your app has, the slower React Native becomes. Each change that you make, gets implemented from one React Native build to another.

And, that consumes resources and keep slowing down your app’s performance.

React Native Architecture

To understand the performance limitations of React Native, we must understand the internal architecture and how it works.

React-native-architecture

React Native app’s typical architecture can be diviided into two different parts:

  • Native Part or Native Realm
  • React Native Part of React Native Realm

Native Realm

This is where you use a platform-specific language part that uses native language based components.

React Native(JS) Realm

Everything inside of it is built around Javascript, that includes everything from javascript based animations to other UI components.

React Native app performance issues

React native seems to offer a promising solution for those who want a viable framework to develop Hybrid applications. Within a short span of time, it has surpassed the likes of other platforms like Xamarin, and Ionic.

However, there are some performance issues that developers and product owners should know of before starting with react native app development. With this article, we aim to cover some of the common performance issues and how you can eliminate them.

React Native Memory Leakage in Android

Your app, in order to perform a specific function(like loading more clothing options in an e-commerce application), consumes some memory from RAM. This RAM memory should be free as soon as the app finishes loading 10 more clothes. Let’s say your app is unable to release the RAM memory it consumed when it tried loading 10 clothes, and it locks 10MB of RAM memory – This is called Memory leak!

Now, your app’s user surfed for another 10 clothing lists, and another 10MB or RAM gets locked. This gradually will stress-up the mobile processing resources to a point that the user gets frustrated enough and deletes your application.

Example of memory leak in a music application

There are a couple of reasons for which memory leaks can be induced in your react native app. Take the case of scrolling in an hybrid android music application for example. Here the scrolling operation takes place in the native part of the hybrid application, and rendering music listing rows happens on the Javascript part of the hybrid application.

The result: On scrolling down the list of songs upto the 3rd page, the app starts freezing up RAM on mobile and eventually degrades performance.

Since both operations occur on different parts(one in native, other in Javascript or React native part), the information has to be passed between them for a smooth operation. Passing this information between these two parts happens over a bridge and consumes considerable time.

When your user scrolls fast enough, the information transfer request over the bridge has to be queued. This prevents the app from freeing up RAM even after the task at hand has been over. There has been a flaw in implementation of ListView on React Native which doesn’t frees up memory consumption from UI elements that goes out from the view itself.

How to fix android memory leaks with scrolling lists?

One way of fixing this is by using FlatList, SectionList, or VirtualizedList instead of using ListView.

FlatList is particularly awesome when you:

  • Are building Infinite scroll pagination
  • Are building Pull to refresh
  • Need smooth rendering performance

While FlatList is simplistic and performant, another high performing interface to build better scroll experience is SectionList. But, it fails when it comes to load large dataset like rows of contacts.

Below you can see SectionList in action. Observe how the performance glitch while scrolling contacts.

JavaScript thread, and heavy CPU Usage

For the sake of simplicity, let’s understand threads as the smallest unit of process execution in an application.

A typical react native app has multiple threads, often interconnected. When these processes/threads are interconnected, they have to remain in sync with each other. A hybrid react has two type of threads:

  • Javascript threads for hybrid part of the application
  • Native threads for the native part of the application

Delay in updating app UI

React native by default offloads all complex functions to the Javascript thread. Often, this causes a delay in updating the app’s user interface.

Under heavy loads and complex operations, such offloading fails to maintain app’s performance. And when that happens, the app stop responding to user inputs and performance lags are extremely apparent.

The same offloading can drastically impact animations within the app from loading. The main javascript thread if isn’t free, navigation and layout related animations won’t launch.

React Native’s team has promised to move animations to the main thread to improve the performance.

Performance issues due to information between threads

The worst bit that we have encountered is that you can only pass strings between threads in JS.

So, when you pass images, data elements, and API data through JS thread, you would notice the overload of serialization and deserialization process.

That’s another reason for UI slowdown and a bad app performance.

React Native application size and performance

These apps heavily reply on third party sources, multiple screens, libraries, etc to operate. These added resources have a direct impact on the application size, increasing the size.

To optimize the application size of a react native application, you should:

  • Use Proguard to minimize the application size.
  • Create reduced sized APK files for specific CPU architectures. When you do that, you app users will automatically get the relevant APK file for their specific phone’s architecture. This eliminates the need to keep JSCore binaries that supports multiple archiectures and consequently reduces the app size.
  • Compress images and other graphic elements. Another alternative to reduce image size is using file types like APNG in place of PNG files.
  • Don’t store raw JSON data. Compress it or convert it into static object IDs.
  • Optimize Native libraries.

Moving Components from Native Realm to React Native Realm

For Native and React Native components in the app to interact, they have to communicate via a bridge. For app developers, it is a bit of a challenge to keep both in sync.

Open source libraries and performance

Often open source libraries needs to be checked for their stability before incorporating them within a react native app. A poorly written open source library could disturb synchronization between native and react native parts of the application.

Message Queue between native and react native components

We have found that some components make heavy use of MessageQueue, so developers need to limit the passing over the bridge and keep the channels different for different elements.

For example, you shouldn’t pass images, or other graphic elements back and forth, which are too heavy to go on the same channel which keeps both Javascript and Native Realms in sync.

Rendering Native views faster

Moreover, developers have to take advantage of React Native’s Virtual DOM feature.

Virtual DOM allows you to optimize the JS rendering components, and batch it asynchronously with their diff algorithm. This effort minimizes the amount of data being sent over the bridge and improves the rendering and syncing of both the Realms.

This algorithm was first built for Javascript based web applications, but many applications including UberEat and F8 have used it to optimize the messageQueue and improve their app’s performance.

Using Navigation components in React Native Android

Ever since the launch of React Native, its core development team had to experiment a lot on navigation to remove inconsistencies between hybrid and native navigation elements. There are multiple performance issues associated with navigation that needs optimization as well.

There are 4 main navigation solutions provided by React Native:

  • Navigator: Not powerful enough to deliver performance required for
  • NavigatoriOS: Only for iOS, not available for Android
  • NavigationExperiment: Designed to solve multiple issues, but failed developers of Airbnb found it difficult to work with. Overall, good for simpler app navigation
  • ReactNavigation: Best overall, you would love if your navigation logic is going to work with Redux

If you ever wondered about the navigational difference between React Native and Native screens, here’s a GIF that explains the difference visually:

React native navigation performance

If you wish to explore these navigation solutions deeply, go through below section. Else, you can skip to the next one!

Navigator

Navigator is a pure JS navigation component. Though, many developers find it confusing as sometimes it is not that powerful in terms of performance.

For instance: OurSky, a web development company based in Taiwan had to ditch Navigator component for their first client project as it is not suitable in cases when you have to keep a common navigation code between Android and iOS.

The main performance issue comes out in their case when they had to add more features to the app due to which multiple operations fall on the JS thread.

So, they choose ReactNavigation, which works well with redux and provided them an improved performance satisfaction.

NavigatoriOS

NavigatoriOS was mainly designed as native for IOS. So, the actual problem started when Android comes into the picture. This limits it suitable only for the cases when you are working around with iOS as a platform.

NavigationExperimental

NavigationExperimental was designed as a highly configurable navigator but it was more confusing for most developers to understand and grasp.

It was introduced as a promising solution for the react native navigation problem but it certainly failed to solve AirBnB’s navigation problem.

Navigation components mentioned above can still work on your app, given that your app is based on simpler UI and not much-complicated operations.

The major problem will be encountered when your app requires the native feel and functionality which only NavigatorIOS can give you (provided that your app is based on iOS).

React Navigation

A much better solution for navigation in react native is React Navigation which boasts of a customizable JS re-implementation of the native views.

React Navigation provide routers that come very handy when you need to put your navigation logic into redux. It can be your alternative solution to all the above navigation components.

Further improving navigation in React Native

While above solutions are helpful, in engineering apps for high growth organizations we kept on coming across problems that were extremely unique. These navigation components howsoever good were still running on the javascript part of the apps.

Luckily for us, Airbnb faced similar challenges and created native-navigation.

native-Navigation has been built on top of native components, which means that your React native app’s navigation performance would be much higher.

Some other benefits of using this library falls into deployment scenarios, where you are not following a mono repo model but are following a multi repo model. Under multi-repo model, you have separate codebase for each platform(iOS, Android).

In case you are using tools like Create React Native App or Expo to develop your app, you must use React Navigation while if your react native app requires the exact feel and touch which only a native app can give then you must use Native Navigation.

Improving React Native App Launch times

Improving app launch time is an endless cycle of evaluating each and every component, looking for better performing libraries, reducing dependencies, etc. Often, an app that launch time is one of the biggest factor contributing to user churn.

Let’s see how you can launch your react native app faster.

Observe default implementations(Finalizers) in React Native

A major source app performance and delays on lunch times can be attributed to the default implementations of react native. React native uses Finalizers that brings in unpredictable behaviour in apps. As software engineers and product owners, it is one of our goals to remove unpredictability from application’s behaviour upon deployment.

The first thing we would suggest you do to improve app’s launch time is to take care of Object.Finalize element. This is the most common cause of slow launch times and poor performance for users.

Even a minor use of Finalizers can lead to out of the memory errors when there is significant memory available. Developers have frequently reported about the number of finalizers at the app startup. The numbers have gone to a couple of thousands.

Imagine, there are 5000 finalizers are waiting to be executed. Finalizers run on a single thread, so every other object has to wait until all finalizers have passed through, before they can be garbage collected. This creates huge dependencies, so it’s recommended not to use finalizers.

Device orientation changes in React Native

Some React Native applications crashes on changing the screen orientation from portrait to landscape. This might be a deal breaker for many users, especially in case of gaming or video applications.

Initially, we saw react-native-orientation as one of the solution to this challenge. But, we quickly discover that on iOS react-native-navigation isn’t able to determine the orientation lock. The library instead fetches orientation information from accelerometer data, which often is inaccurate – failing to give us real UI orientation.

Changing the orientation when view’s layout has been changed

More success with device orientation changes can be achieved by listening to the app’s root view. This implementation only fires an orientation change when actually the device’s orientation is changed.

Animating the size of images for different UI view

In React Native applications, every time a user adjust the width or height of an image, it is re-cropped and scaled from the original image size.

This process consumes a lot of memory and the app takes few seconds to load, especially for large images.

To tackle these changes, most developers use transform: [{scale}]  style property to dynamically fit the image sizes for different UI view. Moreover, to make it more memory efficient, you have to implement a custom FadeInImage component to display a tiny shade.

In this approach, React Native applications can quickly project images for different UI view using the onLoad component and the cached fade in image.

Multithreading and React Native performance

Going back to Javascript threads, React Native doesn’t supports multiple threads (small unit of process) at the same time – This is also known as multi-threading.

When React Native is rendering one component, other components have to wait till the first one is rendered.

Twitch had to move away from React Native because of these challenges. When they started implementing live chat feature that runs along parallel to a live video feed, they faced major performance bottlenecks. This impacted low-end devices even more.

react native performance with live video and chat Twitch
Credits: Twitch.tv

This error occurs when the Javascript thread is not free to communicate with the Native thread and UI. Multi-threading support is currently in the future roadmap of React Native’s team.

Build your own extension code to take care of multi-threading

While the support for multi-threading isn’t there, we have found writing our own extension code with heavy focus on system design and maintainability a viable solution.

With careful consideration, you can easily write an extension code that creates a bridge between React native and native components. In a Twitch like scenario, you would basically port these conflicting and performance lagged operations on the native parts of your app. That way, your react native components won’t come under stress and there won’t be performance lags.

These extensions can be easily written using:

  • Java
  • Objective-C
  • Swift

We are currently trying to build these extensions using Kotlin as well.

Infinite Scrolling: performance optimization for React Native

React-Native-Infinite-Listview

As we wrote before, a big part of software engineering is to optimize for performance. Infinite scrolling’s default implementation in React native is often the biggest performance bottleneck for newsfeed type of applications.

There are two ways with which you can implement infinite scroll:

  • ScrollView: It renders all list/feed elements at once
  • ListView: Brings in additional rendering options(re-loading elements pre-defined)
  • FlatList: For react native versions > 0.43

Talking about performance issues with infinite scroll options

ScrollView: Loading all elements at once impacts memory usage and performance

ListView: Loads UI elements better than ScrollView, but isn’t good enough for highest possible performance

FlatList: Only load a UI component or a list item when it is needed by the user, better known as Lazy loading.

Further improving the scroll experience in React Native

Chop, an ecommerce startup based of San Fransisco came out with a solution which was based of Brent Vatne’s experiment for Fixed height infinite list.They implemented a sliding window with the list in such a way that when the screen gets scrolled the sliding window moves up, but the items remain fixed at their respective positions.

Since, the items are not moving hence the entire memory usage is of no issue here.

Image caching in React Native

Image caching is important for loading images faster. As of now, React Native only provides image caching support on iOS.

Implementing image caching in React Native for Android

Though there are some npm libraries that attempts to solve the image caching issue in android by implementing caching logic in JS and storing images on file system. But, they often fail to provide the optimal performance we desire to deliver a delightful app experience.

The challenges most of these libraries bring to image caching on Android are:

  • Cache miss: Whenever an app’s page is refreshed, often these libraries fail to fetch previously loaded images.
  • Performance lags when the caching logic is ran on the javascript side of application

Improving image caching on android

Looking at other alternatives, react-native-cacheable-image brings dynamic caching with placeholder based implementations. Another improved version of this library can be found with react-native-cached-image.

Image Optimization in React Native

When you work around in react native, it is very important to use responsive images. Otherwise, you may find your with an app that delivers sub par performance and multiple error messages.

Zeemee engineering team while developing their app with react native discovered that photos in the user profile sections would constantly flash.

React native image optimization

Lessons learned from Zeemee’s experience

Use smaller sized images: This happened because they are using larger images due to which OS would load the image for some time and then remove it while reloading other. To fix this, they had to request smaller images from the server.

Use PNG as opposed to JPG: Using PNG files as a static image of your react native app will also lead you to the problem of memory leaking. This is because react native use fresco library to render and display images. This can be solved by using JPG images which will reduce the memory footprint as a result.

Convert your images to WebP format: Another alternative fix to this issue is to convert your images into WebP format. WebP format support both lossy and lossless compression techniques which are best for compressing the size of images. WebP images can speed out your image loading time to 28%.

WebP images can speed out your image loading time to 28%.

Image Placeholders and Progressive Image Loading

Image Placeholders in React Native

Your app image gallery will require image placeholders in situations where there is any possibility of running it in a slow internet connection or in usability cases such as a news app or e-commerce app where you have larger image grids.

In case if you aren’t aware about image placeholders in app’s, the below illustration would better help you:

react native image optimization: Placeholders

Though, react native’s image component let you implement image placeholders via a defaultSource property. But the problem is it is only available for iOS devices.

For Android devices, there are few NPM libraries that implements defaultSource in Android.

Progressive Image loading

When you use react native’s image component then it will provide you a plain user experience which is not a good user experience.

To overcome this, organizations such as Facebook and Medium use progressive loading technique in mobile which is based on a simple idea as follows:

  • Use a tiny thumbnail to display on your placeholder – You can do this by returning the markup of this tiny thumbnail in the initial HTML as <img/>, so that it can be fetched by browser easily
  • Use the Progressive image component – Next, you can use your progressiveimage logic to load the thumbnail first and then fade it for sometime when the actual image is ready for displaying

The entire code for the progressive image loading is available on GitHub here.

Using cleaned console.log statements

Though console.log( ) statements are important to debug your react native app but these statements in huge numbers can cause performance lag in your react native app.

The reason being that these pieces of code are synchronous so they might slow down your app.

You can remove all the console calls by using babel plugin.

Third party SDKs and libraries and React Native performance

Most React Native apps have to leverage some combination of network image loading, crash tracking, advertisement, or analytics libraries and SDKs.

To avoid re-inventing the wheels, developers have to use third-party Android and iOS libraries. However, it turns out that many libraries and SDKs cause major startup delays and other performance issues.

These are few common issues that should not be overlooked:

Hung methods

Libraries and SDKs often need to be initialized, so it is convenient for their developers to provide an initialization method that does a lot of the heavy lifting.

For instance, an advertisement SDK may acquire the list of installed apps to avoid showing you an ad for an app you’ve already installed.

If the developers are not careful and do this heavy work in the main thread, the performance would suffer.

Reflection

The developers use Reflection to make it easier for other developers to adopt their libraries. For example, developers can create a REST client just by adding some Java Annotations, but imagine when Java components would run on your single threaded JS realm in React Native.

Working with Maps in React Native

If you’re working with map feature, you would find the dragging and navigation is pretty slow in React Native.

So, Follow these practices while integrating map library for a better React Native performance:

  • Remove the console.log: Just to make sure that it’s not logging lots of data in Xcode. If it does, the performance will suffer.
  • Replace region for InitialRegion element in the map: By default, it keeps updating the region, and that creates a heavy load on the JS thread.

React Native App Initial Load View

Whenever you launch a React Native app, not only does it have to load the native runtime, but it also has to load a JavaScript Context and do the initial rendering.

That time used to render the initial RootView sometimes results in a white screen flash. This is specifically more visible in applications where developers haven’t used any splash screen.

Fortunately, there is a property that can be used on the Root View to display something else during the load time. That property is not really documented but has been around for a while now.

It’s known as loadingView and is present in the RCT Root View class.

API JSON data optimization in React Native

Mobile applications always need to load resources from a remote URL or service and to perform such actions, developers make fetch requests to pull data from that server.

The fetched data from public and private APIs returns in JSON form that has complex nested objects. It’s complex to deal with this kind of data structure especially when you’re building an offline react native application using Flux, Redux, or Mobx.

Most of the developers store the same JSON data locally for offline access, and the performance suffers because Javascript applications render JSON data slowly.

Also, storing data in JSON format is not scalable for any application.

To convert JSON data schemas into object IDs, you have to use a third party library(like Normalizr), and store locally for a better performance.

Frozen UI in React Native

Frozen UI occurs when there is a long operation running on the main thread, and the main thread blocks UI thread to render. This situation gets even worse while rendering custom animations, large files, raw JSON data or lots of map data.

The application stops responding when there is:

– Heavy load on JS thread

– Heavy load on message queue(the bridge)

When UI freezes, the users would not be able to navigate or perform an action. For countermeasures, you have to make sure that no heavy rendering is happening on the main thread.

Don’t use Object Finalizers, because they further slow down the rendering process.

Handling Animations in React Native

React Native offers an animated API to build and run basic stock animations like layout transitions. But, to build custom animations you might be using third party libraries like this one.

When you use third-party APIs or libraries, the frame rate drastically slows down and so is performance. Moreover, in some cases, it drains the battery faster than the usual.

One way of dealing with animations in react native is to ensure that the framerate of your app never or rarely dips below 60fps.

Following are some performance issues related to frame rate drop which generally occur while you play around animations in React Native:

Heavy computation work on JS thread

A frame will be usually dropped in cases when you are trying to do something heavily on computation. For example, when you render a new scene with multiple child components, there are stronger chances of a drop in the frames.

This can be fixed by using an Animated library with Native driver. With this approach, you can improve your serialization/ deserialization time. Moreover, the performance will improve drastically as you are doing most of the interpolation work in native thread

Slow navigator transitions

In react native, javascript thread controls navigator animations. Slow navigator transitions take place when react is trying to render new screen when the animation is running on Javascript thread.

This locks up the javascript thread and slitters animations.

Using interaction manager will considerably improve your performance in cases like slow navigator transitions. However, if your user experience is suffering way more than expected, then you should better stick with Layout Animation.

React Native app responsiveness on a keyword popup

When you’re working with React Native apps, a common problem is that the keyboard will pop up and hide text inputs. The users tend to go back and forth and it creates load on the JS thread, and hinder the user experience.

Aside from a minor performance issue, this also looks annoying from a user’s perspective.

To avoid these situations, there is a component that you can install “KeyboardAvoidingView”.

You can take the base code, which has the keyboard covering the inputs, and updates that so that the inputs are no longer covered.

The first thing you have to do is replace the container View with the KeyboardAvoidView and then add a behavior prop to it. If you look at the documentation you’ll see that it accepts 3 different values — height, padding, position.

You can experiment with all three variables, and choose the one which suits your application.

Conclusion

React Native is a complex platform in terms of its architecture of different components, libraries, and Native modules. But, ever since the inception, many big organizations have trusted React Native to develop their mobile applications.

That commitment shows that React Native does have the potential to be the preferred choice for building high performing applications.

We have put together all major performance issues, along with countermeasures to improve your app’s performance.

Hope this article shed some light on improving your app’s performance, and push your knowledge of the platform a little further.

Have any questions left? Feel free to drop a link in the comments section.

Purvak Pathak

When it comes to mobile applications, few have the rare attention to detail like Purvak. He frequently shares his thoughts about building mobile applications on Linkedin.

  • Mark Caple

    Thanks Purvak great set of rules to follow. Fortunately or unfortunately I had already decided on react-native-navigation. Hope we’ve chosen the right option. 🙂

    • purvak_simform

      Hey Mark,
      Thanks for kind words. I am certain that your engineering team can easily optimize for performance with react-native-navigation. Would to hear back on your journey with React native.

      Anyways, if you run into any problems, feel free to reach out. 🙂

  • scarlz

    I would strongly have to disagree with any recommendation for using React Navigation, not least that it’s the “best overall”. It has fundamental design flaws, performs poorly, is missing too many core features, development is very slow, and long-standing issues/pull requests are ignored or outright closed without action. It simply is not production ready.

    I was lured into its use on the basis that, in being the official choice for navigation on React Native, I couldn’t go far wrong with it. How wrong was I. You will spend more time fighting with / hacking around with that library only to produce sub-par results than anything you’ve ever known.

    • purvak_simform

      Hey @scarlz:disqus
      Agree with you on React Navigation, we were lured as well. In some cases, it worked wonders, in others – it didn’t. That’s why we listed alternatives to React Navigation.

      Anyways, I would love to learn about you experience with react native limitations? Care to share some of your bits on purvak@simform.com?

React-Native

Get insights on building high performing, maintainable react native apps

Learn how you can improve your app's performance, increase speed to market and build apps that your customers love!

You have Successfully Subscribed!