Performance is a topic with major ramifications for using a framework like React Native in real-world mobile applications. Simply put, React Native is fast by default. While working on a React Native app, however, you might experience performance issues.

Do not assume these issues can be fixed by testing components. In this post, we'll offer a list of suggestions for optimizing performance while building a React Native app.

DO: Use an Image Caching Solution

React Native offers an Image component as part of its core set of components. This component is used to display an image, but, out of the box, it does not have a solution for issues like:

  • rendering a lot of images on one screen
  • low performance in general
  • low-performance loading from cache
  • flickering

The Image component in React Native handles caching images like web browsers, which is sometimes the cause of the above issues. They are easily resolved by using a third-party library called react-native-fast-image. It is available for both iOS and Android and is efficient at caching images.

DO: Use appropriate image size

Optimizing an image is important for a React Native app's performance if the app relies on using a huge amount of images. Rendering lots of images could lead to high memory usage on a device if the images are not appropriately optimized in terms of size. This may cause the app to crash.

Some things that can be done to optimize images in a React Native app include:

  • Use PNG format instead of JPG
  • Use smaller-sized images
  • Use WEBP format for images. It can help reduce the binary size on iOS and Android by 29%.

DO: Avoid unnecessary renders

React Native is based on the React library and handles rendering components in a similar way to React.js. Therefore, the optimization techniques that are valid with React also apply to React Native applications. One optimization technique is to avoid unnecessary renders, and in functional components, this can be done by using React.memo().

React.memo() is used to handle memoization. The concept of memoization is described as follows: if a component receives the same set of props more than once, it will use previously cached props and render the JSX returned by the functional component only once.

For example, consider the following parent and a child component. The Parent component has a state variable called count that is updated when the button is pressed.

Whenever the button is pressed, the Child component also gets re-rendered even though its prop text does not change on each render. It is not doing anything special to its parent component and is just displaying some text. This can be optimized by wrapping the contents of the Child component with React.memo().

// Parent.js

const Parent = () => {
  const [count, setCount] = useState(0);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title='Press me' onPress={() => setCount(count + 1)} />
      <Child text='Placeholder text' />

// Child.js
const Child = React.Memo(({ text }) => {
  return <Text>{text}</Text>;

DO: Use nativeDriver with Animated library

There are many ways to create animations in a React Native app. One of the most popular ways to do this is to use the Animated library.

Animated uses nativeDriver to send animations over the native bridge before animation starts. This helps the animations to execute independently of a blocked JavaScript thread, thus resulting in a smoother experience without dropping many frames.

To use nativeDriver with the Animated library, you can set its value to true. In the example below, useNativeDriver is used on an onScroll Animated event in a ScrollView component.

    [{ nativeEvent: { contentOffset: { y: animatedValue } } }],
    { useNativeDriver: false }
  // Component's content

DO: Use Flipper to debug issues

React Native version 0.62.0 introduced a new tool called Flipper. It's a debugging platform for iOS, Android, and React Native apps. It integrates directly with the native code, and its integration with a React Native app is enabled out of the box.

Using Flipper to debug apps does not require remote debugging. It requires a locally connected instance of Metro to interact with the React Native app. It has React DevTools to inspect the component tree and check out the state and props of a React component.

It uses a native plugin ecosystem for debugging both iOS and Android applications. These plugins are used for device logs, crash reports, inspecting network requests, inspecting the local database of an app, inspecting cached images, etc.

DO: Use Hermes

Hermes is an open-source JavaScript engine optimized for mobile applications. Since React Native version 0.60.4, Hermes has been available for the Android platform. It helps with reducing the download size of an app (APK for Android), reduces memory consumption, and reduces the time it takes for an app to become usable (TTI - Time to Interact).

To enable the Hermes engine in an Android app, open build.gradle and modify the following:

def enableHermes = project.ext.react.get("enableHermes", true);

Since React Native version 0.64-rc.0, Hermes is also available to be used on the iOS platform. To enable it for iOS, open Podfile and modify the following code:

use_react_native!(:path => config[:reactNativePath], :hermes_enabled => true

DON'T: Leave console statements in the source code

Using console.log statements is one of the most common methods to debug in JavaScript applications in general, as well as in React Native apps. However, leaving the console statements in the source code when building a React Native app for a platform could cause some big bottlenecks in the JavaScript thread.

One way to keep track of console statements and remove them is to use a third-party package called babel-plugin-transform-remove-console. To use it, install the package by using the following command in a terminal window:

yarn add babel-plugin-transform-remove-console

Then, modify the .babelrc file to remove all console statements:

  "env": {
    "production": {
      "plugins": ["transform-remove-console"]

DON'T: Use Scrollview to render a huge list of data items

There are few ways to create scrollable lists in React Native. Two of the common ways available in the React Native core are ScrollView and FlatList components.

A ScrollView component is simple to implement. It is often used to traverse over a list of finite number of items using a JavaScript's map() function. For example:

  { => {
    return <Item key={} />;

The ScrollView component renders all children at once. This is good for use cases where the number of items in a list to render is quite low. Dealing with a large amount of data can directly affect the performance of the app.

To deal with large lists of items, React Native provides a component called FlatList. This component ensures that the items are lazy loaded such that the app does not consume an inconsistent amount of memory.

For example:

  keyExtractor={item => `${}`}
  renderItem={({ item }) => <Item key={} />}


React Native is an open-source framework used to create cross-platform mobile applications. It uses JavaScript at its core and has a primitive API of components to build mobile interfaces and functionalities. It's a high-performance framework as long as you build with performance in mind from the start.

At Crowdbotics, the React Native framework is an integral part of the RAD stack. We use React Native (as well as React Native Web and React Native for Windows + MacOS) to build universal applications from a single codebase, and the Crowdbotics App Builder automatically scaffolds apps with prebuilt React Native screens.

If you're interested in building a custom mobile app completely from scratch, Crowdbotics provides expert PMs and developers to manage your app build. Get in touch with us today for a detailed quote and development timeline.