MWAN MOBILE

×
mwan_logo

How to Get Better Performance Out of Your React Native App

Adopted the world over by massively successful companies like FacebookShopifyCoinbaseTesla, and Discord, React Native offers a promising solution for those seeking a viable framework on which to develop cross-platform applications. Why are some of the world’s largest companies making the switch to React Native?

  • It offers more concise and easier-to-understand code that can be shared across multiple platforms.
  • It offers fast iteration without a compile cycle.
  • You can ship faster and focus on details that really matter, making your app look and feel fantastic.

To reach the true performance potential of your React Native app though, optimization is a crucial step. In this article, we break down some optimizations that can make your React Native app twice as fast and way more efficient.

Improve Start-up Time

App start-up time means the time from app launch to draw content from the app. Decreasing bundle size and memory usage will help to improve the start-up time. We can enhance app performance by improving start-up time.

Hermes

Hermes is an open-source JavaScript engine optimized for RN. We can use Hermes to enhance the start-up time as enabling it will result in decreased memory usage and a smaller app size. Always make sure to use the latest version of RN when using Herms.

Enabling Hermes for Android

For Android applications, add following lines to android/app/build.gradle to enable Herms for Android.

project.ext.react = [
       entryFile   : “index.js”,
   –   enableHermes: false  // clean and rebuild if changing
   +   enableHermes: true  // clean and rebuild if changing
]

If you’re using ProGuard, add these rules in proguard-rules.pro:

-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }

Next, clean the build: cd android && ./gradlew clean

Deploy the app npm react-native run-android

Enabling Hermes for iOS

For iOS applications, add the following code block to ios/Podfile file to enable Herms for iOS.

use_react_native!(
   :path => config[:reactNativePath],
   # to enable hermes on iOS, change `false` to `true` and then install pods
–   :hermes_enabled => false
+   :hermes_enabled => true
)

Install the Hermes pod

‍cd ios && pod install

Deploy the app

npm react-native run-ios‍

useMemo

We can use useMemo hooks to avoid re-rendering, and it helps to prevent re-rendering of child components by returning memorized values of a function. If any component receives the same props more than once, useMemo will use previously cached props and render the JSX view and return the component. Thus, useMemo helps to improve the performance of RN applications. However, it should be used only when performing expensive calculations, as we can memorize the computations to recalculate the results if only the values are changed.

We have used FlatList and Button in the below example. At the first time, FlatList will render perfectly, and when the user clicks the button count, it will increase by one. Then the state is updated, and the whole component will re-render without any change in the array. As in code, we avoid this by wrapping FlatListItem (UseMemoFlatListItem) with useMemo. It will check whether there are any changes in props and render the JSX only if there are changes. Otherwise, it will return the previous props and render the previous view.

import * as React from ‘react’;
import {View, FlatList} from ‘react-native’;
import {useState} from ‘react’;
import UseMemoListItemSeprator from ‘./UseMemoListItemSeprator’;

const data = [
   { name: ‘Sri Lanka’ },
   { name: ‘India’ },
   { name: ‘Australia’ },
];
const [arrCountry, setArrCountry] = useState(data);
const [count, setCount] = useState(0);

function UseMemoFlatListItem({item, onChange, arrCountry}) {
   return useMemo(() => {
       return (
           <View style={Styles.container}><Text>{item.name}</Text></View>
       );
   }, [item.status]);
}
return (
   <View style={Styles.container}><Button title=’Increment’ onPress={() => setCount(count + 1)} /><FlatList
           data={arrCountry}
           keyExtractor={(item) => String(item.name)}
           renderItem={({item, index}) => (
               <UseMemoFlatListItem
                   item={item}
               />
           )}
           ItemSeparatorComponent={() =><UseMemoListItemSeprator />}
           showsVerticalScrollIndicator={false}
       /></View>
);

https://hubs.la/H0DC7rz0


Flipper

Flipper is a debugging platform for Android, iOS, and RN apps. It has a layout and network inspector and shows logs with a clean UI. Flipper integrates directly with native code, so it doesn’t have a runtime difference between JS engines or require remote debugging. We can track functions, methods, and many logical things by installing the Flipper plugin, and it can be installed directly from the desktop app.

Cache Images

RN provides an image as a core component that allows developers to display images. However, there are few issues with this image component. Some of them include rending many images on a single screen, image flickering, low performance in image loading, and cache loading. To solve these issues, we can cache the image and use the local cache in the subsequent request. However, RN supports built-in caching only for iOS and not for Android.

We can cache an image as shown below,

<Image
 source={{
   uri: ‘https://unsplash.it/200/200?image=8’,
   cache: ‘only-if-cached’
 }}
 style={{ width: 400, height: 400 }}
/>

However, this method is not optimal, and still, there can be performance issues. However, these performance issues can be resolved using a third-party library called react-native-image, which supports both iOS and Android apps. The fast image allows users to render images quickly using a cache mechanism. Furthermore, it adds authorization headers and several other features.

import FastImage from ‘react-native-fast-image’

const App = () => (
 <FastImage
       style={{ width: 400, height: 400 }}
       source={{
           uri: ‘https://unsplash.it/200/200?image=8’,
           headers: { Authorization: ‘auth-token’ },
           priority: FastImage.priority.normal,
       }}
       resizeMode={FastImage.resizeMode.contain}
   />
)

Additionally, we can use a library such as react-native-cached-image to cache and load images.

import { CachedImage } from ‘react-native-cached-image’;
<CachedImage
 style={{ width: 400, height: 400  }}
 source={{ uri: ‘https://unsplash.it/200/200?image=8’ }}
/>

It is essential to optimize your app images to improve the performance of the app. The best way to include images in your app is by saving them in appropriate formats, such as PNG or WebP. The WebP format, which was introduced by Google in 2010, is the most performant format among other formats.

Use NativeDriver with the Animated API

Developers are using animations in RN apps, and there are many ways to use animations in apps. However, running animations on the JavaScript thread is not a good practice. The best practice is to use an Animated library and the nativeDriver to push the details of the animation over the native bridge before it starts on the screen. We can use the nativeDriver with the Animated library by simply setting useNativeDriver as ‘true.’

import React from ‘react’
import {Animated} from ‘react-native’
const App = () =>{
   Animated.timing(this.state.value, {
       toValue: 1,
       duration: 500,
       useNativeDriver: true,
   }).start();
   }

Optimize Android App Size

Usually, developers do not bother about app size at the start of the project. Yet, it isn’t easy to make such predictions up to some extent. However, reducing the application size will improve the performance of the application. Therefore, it is vital to use the necessary components and optimize them to reduce the app size. A typical RN app contains resources such as images, fonts, etc. In addition, there’s a JavaScript bundle with business logic and four different sets of binaries compiled for different CPU architectures. We can use proguard to reduce app size, and we can optimize the binary size of the Andriod build by setting the Boolean flag enableProguardInReleaseBuilds to true.

Add the following line in android/app/build.gradle.

‍def enableProguardInReleaseBuilds = true

Add the below line to extract four different sets of binaries according to your CPU architecture.

def enableSeparateBuildPerCPUArchitecture = true


Optimize React Code

Good coding practice and techniques can help make applications better. Therefore, as a best practice, we should avoid unnecessary render calls and anonymous functions. These multiple render calls can lead to serious performance issues. We can use PureComponent to handle them without handling them manually. This kind of component does not modify props or the state inside the component, preventing multiple render calls.

import React, {PureComponent} from ‘react’;
import { Text } from ‘native-base’;

class PureComponentExample extends PureComponent {
 render() {
   return <Text> This is a pure component</Text>;
 }
}
export default PureComponentExample;

Furthermore, if you want to use the same concept in the function component, react.memo has been introduced for the same purpose.

Use of memory optimization

We can monitor memory leaks in RN apps with the help of Xcode or Android Device Monitor. Sometimes, RN applications lead to memory leaks since they contain a lot of background processes. Therefore, we should avoid these background processes as much as possible. For instance, if you want to show a list view, it is recommended to use FlatList, SectionList, or VirtualizedList instead of using ScrollView. FlastList ensures lazy loading of the items and thereby enhances the performance of the app.

FlatList example

import React from ‘react’;
import { SafeAreaView, View, FlatList, Text } from ‘react-native’;

const DATA = [
 {
   id: ‘1’,
   title: ‘First Item’,
 },
 {
   id: ‘2’,
   title: ‘Second Item’,
 },
 {
   id: ‘3’,
   title: ‘Third Item’,
 },
];

const Item = ({ title }) => (
 <View><Text>{title}</Text></View>
);

const App = () => {
 const renderItem = ({ item }) => (
   <Item title={item.title} />
 );

 return (
   <SafeAreaView><FlatList
       data={DATA}
       renderItem={renderItem}
       keyExtractor={item => item.id}
     /></SafeAreaView>
 );
}

export default App;

Use of memory optimization

Using console logs is very common while debugging JavaScript code. However, keeping those console statements while deploying the application will cause huge performance issues due to the JavaScript thread. So, we can use a babel plugin to remove all console statements from the app.  

We can install the babel plugin using the following commands.

npm install babel-plugin-transform-remove-console
# OR
yarn add babel-plugin-transform-remove-console

Then we can modify the .babelrc file to remove console statements from the production env as shown below.

{
   “env”: {
       “production”: {
           “plugins”: [“transform-remove-console”]
       }
   }
}

Make Use of Uncontrolled Inputs

The most common issue with controller input is slowing down devices and rendering glitches while updating the view when a user is typing very fast. Moreover, controller input is slower than uncontrolled inputs as it also deals with native JavaScript threads. On the other hand, uncontrolled inputs in RN are more performant due to no state changes when they are modified.

export default function UncontrolledInputs() {
   const [text, onTextChange] = React.useState(‘Controlled inputs’);
 
   return (
     <TextInput
       style={{ height: 100, borderColor: ‘red’ }}
       onChangeText={text => onTextChange(text)}
       defaultValue={text}
     />
   );
 }

Conclusion

Several factors can affect the performance of a React Native app, such as containing large images, heavy computations, and unnecessary render calls. On the bright side, we can avoid many common performance issues by following best practices and using tools like the ones discussed and recommended in this article.

Source: CROWDBOTICS