Share on Twitter
Share on Facebook
Share on HackerNews

Tips for Optimizing React Native Application Performance - Part 2: Using Sentry SDK for Performance Monitoring

Monitoring performance in front-end applications is vital. It focuses on the areas of the application users experience. These areas are slow rendering or frame rate, network request errors, and unresponsive user experience. Your application’s users expect the experience of using the application to be fast, responsive, and smooth.

In the first article of this series, we discussed some tips for optimizing your React Native application performance. In this part (2), we’re going to explore how to monitor performance issues in your React Native application’s codebase.

In this tutorial, let’s learn how to integrate Sentry SDK in a React Native application and enable performance monitoring.

Pre-requisites

This tutorial requires that you are familiar with the fundamentals of React Native and JavaScript in general. If you don’t already have it setup, please follow these instructions to set up a React Native environment on your local machine.

Getting Started with Sentry

Begin by creating an account, and soon after, click the Create project button.

react-native-part-2-aman-sentry-sdk-image4

Next, select the platform and framework you want to use. In this case, we are using a mobile platform and React Native.

react-native-part-2-aman-sentry-sdk-image6

The last step is to give your Sentry project a name. Once you’re done, click the Create Project button.

react-native-part-2-aman-sentry-sdk-image7

Creating a React Native app

Open up a terminal window, and let’s create a new React Native application:

npx react-native init rnBooksStore

cd rnBooksStore

Your application has to use a routing library to enable routing instrumentation later. For this app, let’s install the react-navigation library:

yarn add @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context

After installing the React Navigation library, make sure to follow instructions from its official documentation for linking the library for both iOS and Android.

Creating screens

In this section, let’s create a mini book store. The first screen will be the Home screen. It renders a large list of books coming from the API endpoint. To illustrate the slow frame rate, the screen component uses ScrollView instead of FlatList.

I’ll be using a long list of book titles from a mock API endpoint: https://example-data.draftbit.com/books?_limit=400.

Create an src/screens/ directory and inside it, create a HomeScreen.js file with the following code:

import React, { useState, useEffect } from 'react';
import { View, StyleSheet, ActivityIndicator, ScrollView } from 'react-native';

import ScreenHeader from '../components/ScreenHeader';
import BookCard from '../components/BookCard';

const HomeScreen = () => {
  const [books, setBooks] = useState([]);
  const [cart, setCart] = useState([]);
  const [loading, setLoading] = React.useState(true);

  const addToCart = book => {
    setCart(cart => [...cart, book]);
  };

  const fetchBooks = async () => {
    try {
      const response = await fetch(
        'https://example-data.draftbit.com/books?_limit=400'
      );
      const json = await response.json();

      setBooks(json);
    } catch (error) {
      console.error(error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchBooks();
  }, []);

  if (loading) {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#0000ff" animated />
      </View>
    );
  } else {
    return (
      <View style={styles.container}>
        <ScrollView contentContainerStyle={styles.listContainer}>
          <ScreenHeader cart={cart} />
          {books.map(book => (
            <BookCard
              key={book.id.toString()}
              thumbnailUrl={book.image_url}
              title={book.title}
              authors={book.authors}
              shortDescription={book.description}
              addToCart={addToCart}
            />
          ))}
        </ScrollView>
      </View>
    );
  }
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingHorizontal: 10,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  listContainer: {
    padding: 10,
  },
  text: {
    fontSize: 24,
    fontWeight: '600',
  },
  headerCartContainer: {
    justifyContent: 'space-between',
    flexDirection: 'row',
    padding: 10,
  },
});

export default HomeScreen;

Creating custom components

The HomeScreen component uses two custom components. The first one displays the count of books when added to the cart. This component is called ScreenHeader. Create a src/components/ScreenHeader.js file with the following code:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const ScreenHeader = ({ cart }) => {
  return (
    <View style={styles.container}>
      <Text>Cart Items</Text>
      <Text
        style={[
          { fontSize: 20 },
          !cart.length ? { color: 'gray' } : { color: '#0000ff' },
        ]}
      >
        {cart.length}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    justifyContent: 'flex-end',
    alignItems: 'flex-end',
    marginLeft: 10,
  },
});

export default ScreenHeader;

The second component renders the info of each book item. Create a src/components/BookCard.js file with the following code:

import React from 'react';
import { View, Image, TouchableOpacity, Text, StyleSheet } from 'react-native';

const BookCard = ({
  thumbnailUrl,
  title,
  authors,
  shortDescription,
  addToCart,
}) => {
  return (
    <View style={styles.container}>
      <View style={styles.flex}>
        <Image source={{ uri: thumbnailUrl }} style={styles.image} />
      </View>
      <View style={styles.itemInfoContainer}>
        <Text style={styles.title}>{title}</Text>
        <Text style={styles.author}>by {authors}</Text>
        <Text style={styles.description} numberOfLines={2}>
          {shortDescription}
        </Text>
        {addToCart && (
          <TouchableOpacity onPress={() => addToCart({ title })}>
            <View style={styles.buttonContainer}>
              <Text style={styles.buttonText}>Add To Cart</Text>
            </View>
          </TouchableOpacity>
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    marginVertical: 10,
    paddingHorizontal: 10,
  },
  flex: {
    flex: 1,
  },
  image: {
    height: 125,
    resizeMode: 'contain',
  },
  itemInfoContainer: {
    flex: 3,
    paddingHorizontal: 10,
    justifyContent: 'space-between',
  },
  title: {
    fontWeight: 'bold',
    fontSize: 14,
  },
  author: {
    color: 'gray',
    fontStyle: 'italic',
  },
  description: {
    color: 'gray',
    fontSize: 12,
  },
  buttonText: {
    color: 'white',
    marginHorizontal: 4,
  },
  buttonContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    borderRadius: 12,
    padding: 8,
    alignSelf: 'flex-start',
    backgroundColor: '#0000ff',
    alignItems: 'center',
  },
});

export default BookCard;

Adding a Home stack navigator

Although we only have one screen in the example application, let’s add a HomeStack navigator so we can learn how to create routing instrumentation configuration.

Create a src/navigation/HomeStack.js file with the following code:

import * as React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import HomeScreen from '../screens/HomeScreen';

const Stack = createNativeStackNavigator();

const HomeStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
    </Stack.Navigator>
  );
};

export default HomeStack;

Integrating Sentry SDK in React Native app

Sentry’s SDK for React Native enables automatic crash reporting and performance monitoring. It captures these issues using the SDK within your application’s runtime.

Open up your terminal window to install and set up Sentry’s React Native SDK:

yarn add @sentry/react-native
yarn sentry-wizard -i reactNative -p ios android

npx pod-install

The sentry-wizard takes care of initializing the Sentry SDK in the React Native App.{js|tsx} file and linking the SDK for both iOS and Android platforms. (Note: the automatic linking only works if your React Native project uses version 0.60 and above.)

By default, the Sentry SDK initialization adds a unique Data Source Name (DSN) string that monitors events in your application.

I’ll modify the Sentry SDK initialization to add a tracesSampleRate property with a value of 1.0. This will enable the tracking of all events in the application. However, in production, ensure that the value of tracesSampleRate is lower than 1.0 to collect a uniform amount of sample data without reaching Sentry’s transaction quota. You can learn more about sampling in Sentry’s official documentation.

Here is an example of Sentry initialization of the App.js file:

import React, { useRef } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import * as Sentry from '@sentry/react-native';

import HomeStack from './src/navigation';

Sentry.init({
  dsn: 'https://da5d189961d145fa91ce878346cce14c@o1267720.ingest.sentry.io/6461490',
  tracesSampleRate: 1.0,
});

const App = () => {
  return (
    <NavigationContainer>
      <HomeStack />
    </NavigationContainer>
  );
};

export default Sentry.wrap(App);

Wrapping the root component of the React Native app like Sentry.wrap() enables automatic performance monitoring. You can also use another high-order component called Sentry.withProfiler() to wrap the root component. It enables tracking the component’s lifecycle as a child span of the route transaction.

Sentry.withProfiler(App);

Enabling routing instrumentation

Routing instrumentation in Sentry’s SDK supports the latest version of React’s Navigation library. The routing instrumentation creates a transaction on every route change. So let’s first add the configuration to enable the routing instrumentation.

In the App.js file, create a navigation reference. It connects to the Navigation container. The onReady() method on NavigationContainer will register the navigation container with the routing instrumentation. Here is what the App.js file looks like after modification:

import React, { useRef } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import * as Sentry from '@sentry/react-native';

import HomeStack from './src/navigation';

const routingInstrumentation = new Sentry.ReactNavigationInstrumentation();

Sentry.init({
  dsn: 'https://your-dsn-string',
  integrations: [
    new Sentry.ReactNativeTracing({
      routingInstrumentation,
      // ... other options
    }),
  ],
  tracesSampleRate: 1.0,
  enableAutoSessionTracking: true, // For testing, session close when 5 seconds (instead of the default 30) in the background.
});

const App = () => {
  const navigation = useRef();

  return (
    <NavigationContainer
      ref={navigation}
      onReady={() => {
        routingInstrumentation.registerNavigationContainer(navigation);
      }}
    >
      <HomeStack />
    </NavigationContainer>
  );
};

export default Sentry.withProfiler(App);

Performance Monitoring in action

Open two terminal windows. In the first window, initiate the React Native packager. In the second one, build the application for either iOS or Android. I’ll be using the iOS simulator to run the application.

# first terminal window
npx react-native start
# second terminal window

# for iOS
npx react-native run-ios

# for android
npx react-native run-android

After the application is built, you will get the following result:

react-native-part-2-aman-sentry-sdk-image3

Now, if you head back to Sentry’s dashboard and navigate to the Performance tab from the sidebar, you will notice it has started to monitor events in our React Native app.

You can click the Mobile tab for the performance overview. It contains information about slow and frozen frames, as well as app start.

react-native-part-2-aman-sentry-sdk-image1

In the screenshot below, you can see the app has started to monitor App start and Home event:

react-native-part-2-aman-sentry-sdk-image8

The Home is the Home screen; since we have routing instrumentation enabled, Sentry will pick up and display the exact screen name. If you click the transaction, you’ll get additional details about what is happening. For example, it will tell you that the starting time of the app is warm from the event app.start.warm.

react-native-part-2-aman-sentry-sdk-image5

On iOS devices, Apple recommends that your app should take a maximum of 400ms to render the first frame. However, our start-up time was a little over 450ms as seen in the screenshot.

Fetching data from the HTTP request (http.client) also takes a little less than one second. Increasing the number of books coming from the API endpoint will increase the start-up time. Thus, large lists of data using a FlatList component can significantly improve the start-up time. Also, pre-fetching data from the API endpoint using a library like react-query can also help improve the start-up time on app restarts.

We can also optimize the BookCard component by caching the image or using a useCallback hook to stop re-rendering the parent component each time a book is added to the cart.

By clicking on “Suspect Spans”, you can view all the transactions in the application:

react-native-part-2-aman-sentry-sdk-image2

Conclusion

Using a powerful tool like Sentry allows you visibilty into your users’ experiences and provides an understanding of how you can improve it. In this article, you set up and integrated the Sentry SDK in a React Native application and learned how to monitor the application for performance with routing (navigation) enabled and network calls. Monitoring the performance of a mobile application is a crucial aspect to engage app users and provide a seamless experience iteratively. It is only possible when these insights are available to us.

Your code is broken. Let's Fix it.
Get Started

More from the Sentry blog

ChangelogDashboardsDiscoverDogfooding ChroniclesEcosystemError MonitoringEventsGuest PostsMobileMoonlightingOpen SourcePerformance MonitoringRelease HealthSDK UpdatesSentry

Do you like corporate newsletters?

Neither do we. Sign up anyway.

© 2022 • Sentry is a registered Trademark
of Functional Software, Inc.