React Native quick start

πŸ‘

Target Platforms

Use this guide for complete steps to install Heap on React Native iOS and Android apps. For web installation instructions, see our web installation guide.

Requirements and Known Limitations

πŸ“˜

For notes on the latest SDK versions, see our React Native changelog.

Please review the following list of requirements and known limitations to ensure your project is set up for this installation process.

The Heap React Native SDK supports React Native 0.63 and up.

iOS

  • By default, iOS apps will only capture React Native autocaptured events and not those captured by the standard Heap iOS integration. While it's possible to capture these events, there are known performance issues in doing so.

  • Visual labeling QR codes can't be configured in managed Expo workflows. Apps built with Expo Application Services (EAS) will need to paired using either the shake gesture in the iOS simulator or the long press gesture outlined on the pairing page.

  • When using the Expo Go app in a managed Expo workflow, events won't be sent to Heap and failure messages will log to the console. Events will only be sent once the app is ejected, either to a bare workflow with expo eject or through the EAS build process.

  • To pair visual labeling using an iOS QR code, you must complete the installation steps in Enable visual labeling pairing from the iOS Installation Guide.

Android

  • Android apps will only capture React Native autocaptured events and not those captured by the standard Heap Android integration. (E.g., Fragment transitions won't be captured.)

  • When using the Expo Go app in a managed Expo workflow, events won't be sent to Heap and failure messages will log to the console. Events will only be sent once the app is ejected, either to a bare workflow with expo eject or through the EAS build process.

Web

Our React Native framework doesn't currently support web implementations. For web implementations, see our web installation guide.

πŸ“˜

How does upgrading the iOS or Android SDK work if I have Heap installed on React Native?

Each version of Heap for React Native is tied to a specific iOS or Android SDK version that it depends on. To upgrade to a specific SDK version on either platform, you'll need to reference our React Native changelog to find the React Native version that ties to the correct iOS or Android dependency.

If you don't see that version listed, please reach out to [email protected].

Expo Framework

Specific platform limitations with managed Expo workflows are covered in previous sections but the important considerations with Expo are:

  • Heap uses native modules that aren't available in Expo Go. Events won't be logged while using Expo Go and failure messages will be logged when events aren't logged. This won't impact other app behavior but limits your ability to test a Heap integration in a managed workflow.

  • When using a managed workflow, it's possible to validate Heap events by either temporarily ejecting to a bare workflow to produce simulator or device builds, or by validating off of an EAS build.

  • Heap for Android requires additional configuration after ejection, discussed in the configuration section below.

  • To use EAS Build with Android, please add the @heap/react-native-heap plugin to your plugins array in app.json

There are no specific issues known for bare or ejected workflows.

Installation

To install Heap for React Native:

  1. From the command line, install the NPM module by running npm install --save "@heap/react-native-heap".

  2. For autocapture, add the following plugins to your .babelrc or babel.config.js configuration:

{
  "plugins": [
    "add-react-displayname",
    "./node_modules/@heap/react-native-heap/instrumentor/src/index.js"
  ]
}
module.exports = {
  plugins: [
    'add-react-displayname',
    './node_modules/@heap/react-native-heap/instrumentor/src/index.js',
  ],
};
  1. Add Heap to the app.json plugins array:
"plugins": [
  "@heap/react-native-heap"
]
  1. For additional information on how to capture component props, see the section below on Prop Capture.

  2. For React Navigation versions 5 and above, to autocapture screenviews, import Heap and wrap Heap.withReactNavigationAutotrack() around the NavigationContainer:

import Heap from '@heap/react-native-heap';

const HeapNavigationContainer = Heap.withReactNavigationAutotrack(
  NavigationContainer
);

Then replace your NavigationContainer tags with HeapNavigationContainer tags.

For React Navigation versions below 5, wrap the AppContainer (the result of a call to React Navigation’s createAppContainer() method) with Heap.withReactNavigationAutotrack().

import Heap from '@heap/react-native-heap';

let AppNavigator = createStackNavigator(
  {
    LoginView: { screen: LoginView },
    MainMenu: { screen: MainMenu },
    SearchView: { screen: SearchView },
    ProductView: { screen: ProductView }
  },
  {
    initialRouteName: 'LoginView'
  }
)
let App = Heap.withReactNavigationAutotrack(createAppContainer(AppNavigator));

iOS

With React Native autolinking, Heap should work on iOS without any changes needed to the native iOS code.

If using React Native without Expo, run the following to install Heap's native module:

cd ios
pod install
cd ..

Android

With React Native autolinking, Heap should work on Android without any changes needed to the native Android code.

If using React Native without Expo, run the following to install Heap's native module:

react-native link

If using React Native or an ejected Expo workflow, add the following line to android/app/src/main/res/values/strings.xml, replacing com.your_package_name with the package name from the manifest tag in AndroidManifest.xml. If you are using the EAS build process, this step is done for you by the plugin:

<string name="com.heapanalytics.android.buildConfigPkgName">com.your_package_name</string>

This step is required for Heap to capture the App Name and App Version on Android.

Configuration

Automatic Initialization

πŸ“˜

We recommend auto-initialization over manually setting your Heap app ID within app code. Manual initialization is also supported as described the Manual Initialization section below.

The library needs to be initialized so events are sent to the right app ID. Place a heap.config.json file at the root of your application’s repository with this structure, substituting your own app IDs.

{
  "default": {
    "heapAutoInit": true
  },
  "dev": {
       "heapAutoInit": true,
       "heapAppId": "YOUR_DEV_APP_ID"
   },
   "prod": {
       "heapAutoInit": true,
       "heapAppId": "YOUR_PROD_APP_ID"
   }
}
  • Note that different app IDs can be set for development and production. Heap can also be enabled/disabled on a per-environment basis. Values in default are used if a key is missing in either dev or prod.
  • The library distinguishes between dev and prod builds using the DEV variable.

If you are seeing runtime warnings at startup mentioning Heap: Could not find BuildConfig, add a line to the resources section of res/values/strings.xml as described in the Android installation instructions.

Manual Initialization

If you’d like finer-grained control over when the Heap library initializes, call Heap.setAppId with an app ID. (Most users won’t need to do this.)

import Heap from '@heap/react-native-heap';
Heap.setAppId('YOUR_APP_ID');

In addition, find the MainActivity.java file, which is React Native’s Android-specific entry point. This file is typically present in a subfolder of android/app/src/main/java/. Add the following imports:

import com.heapanalytics.reactnative.RNHeap;
import android.os.Bundle;

Next, add an onCreate method containing the following. If an onCreate method already exists, β€Œ add the RNHeap.init line to it.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RNHeap.init(getApplicationContext(), "<YOUR_APP_ID>");
}

Ignoring Sensitive Data and PII

On React Native, autocaptured data will include interactions with certain types of components, as well as capturing target text from components if there is any to capture. Additionally, commonly-used props for popular UI libraries, like React Native Elements or NativeBase, may also be captured as a part of the component hierarchy event property. The result is that some care is needed when dealing with components that contain sensitive information or other PII.

The Heap for React Native SDK offers several methods to make sure that PII isn't sent from a React Native app to Heap, using React components that may be used within JSX code or via higher-order components.

HeapIgnore and HeapIgnoreTargetText Functional Components

The Heap for React Native SDK provides two convenient functional components for restricting the data that is autocaptured by Heap.

  1. The HeapIgnore component is the most restrictive component, and can be used to stop Heap from capturing any events below the ignored component in the hierarchy.

For example, consider a component that has an extremely sensitive piece of information in some button text:

import React, { Component } from 'react';

class MySensitiveScreen extends Component {
  render() {
    const safeProp = this.props.profile.randomUuid;
    const superSensitiveProp = this.props.profile.creditCardNum;
    const { styles } = this.props.styles;

    return (
      <View style={styles.container}>
        <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
          <View style={styles.button}>
            <Text style={styles.buttonText}>{superSensitiveProp}</Text>
          </View>
        </TouchableHighlight>
        <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
          <View style={styles.button}>
            <Text style={styles.buttonText}>{safeProp}</Text>
          </View>
        </TouchableHighlight>
      </View>
    );
  }
}    

export default MySensitiveScreen;

As written, Heap would capture any interactions with both of the buttons and set Target Text event properties with the content of the Text components. We can re-write this component to use HeapIgnore:

import React, { Component } from 'react';
import { HeapIgnore } from '@heap/react-native-heap';

class MySensitiveScreen extends Component {
  render() {
    const safeProp = this.props.profile.randomUuid;
    const superSensitiveProp = this.props.profile.creditCardNum;
    const { styles } = this.props.styles;

    return (
      <View style={styles.container}>
        <HeapIgnore>
          <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
            <View style={styles.button}>
              <Text style={styles.buttonText}>{superSensitiveProp}</Text>
            </View>
          </TouchableHighlight>
        </HeapIgnore>
        <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
          <View style={styles.button}>
            <Text style={styles.buttonText}>{safeProp}</Text>
          </View>
        </TouchableHighlight>
      </View>
    );
  }
}    

export default MySensitiveScreen;

By wrapping the TouchableHighlight that contains the button with sensitive text in a HeapIgnore component, you can prevent Heap from capturing any interactions from the button. Interactions with the other button, which has safe target text, will still be sent to Heap. The HeapIgnore component can be used to ignore events very broadly, as well as very specifically. For example, wrapping the entire App component in HeapIgnore would ignore all autocaptured events from the entire application. For more specific use cases, the HeapIgnore component can be used to ignore events from a specific button.

  1. Sometimes, however, it's important to be able to capture the fact that a user interacted with a button or other component, and you just want to ensure that the target text itself is not captured.

In these cases, the HeapIgnoreTargetText component is extremely useful:

import React, { Component } from 'react';
import { HeapIgnoreTargetText } from '@heap/react-native-heap';

class MySensitiveScreen extends Component {
  render() {
    const safeProp = this.props.profile.randomUuid;
    const superSensitiveProp = this.props.profile.creditCardNum;
    const { styles } = this.props.styles;

    return (
      <View style={styles.container}>
        <HeapIgnoreTargetText>
          <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
            <View style={styles.button}>
              <Text style={styles.buttonText}>{superSensitiveProp}</Text>
            </View>
          </TouchableHighlight>
        </HeapIgnoreTargetText>
        <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
          <View style={styles.button}>
            <Text style={styles.buttonText}>{safeProp}</Text>
          </View>
        </TouchableHighlight>
      </View>
    );
  }
}    

export default MySensitiveScreen;

In this version of our example component, Heap will capture all of the touchableHandlePress events that occur for both buttons. For the button with the safeProp text, events will contain a Target Text event property with the value of safeProp. However, for the button with the sensitive text, the Target Text event property will be suppressed, and no PII or sensitive information will be captured.

NOTE: HeapIgnoreTargetText still sends through component properties that have been configured for capture. For example, <Button title={sensitiveProp}/> would still capture the title property as a part of the autocaptured event.

Suppressing Target Text is an extremely useful way to avoid sending PII, but can result in the elimination of important metadata for creating virtual events. In cases where some additional metadata is required for specifically creating an event, it is recommended to add a testID property, which will be captured automatically by Heap. These testIDs are typically used for automated testing using tools like Detox or Appium.

HeapIgnore Higher-Order Component

In some cases, it's easier to use a Higher-Order Component (HOC) for applying ignore functionality. For example, if you wanted to ignore all interactions for all instances of a component, but always contain sensitive data, HOCs can be very useful. As an example, we might have a button that often contains sensitive information as its text. We can make sure that those interactions won't be captured by exporting an ignored button using the HOC.

import React, { Component } from 'react';
import Heap from '@heap/react-native-heap';

class SensitiveButton extends Component {
  render() {
    const safeProp = this.props.profile.randomUuid;
    const superSensitiveProp = this.props.profile.creditCardNum;
    const { styles } = this.props.styles;

    return (
      <TouchableHighlight onPress={this._onPressButton} underlayColor="white">
        <View style={styles.button}>
          <Text style={styles.buttonText}>{superSensitiveProp}</Text>
        </View>
      </TouchableHighlight>
    );
  }
}

const IgnoredSensitiveButton = Heap.withHeapIgnore(SensitiveButton);

export default IgnoredSensitiveButton;

Then we can use this in other locations within the app. Using our previous example:

import React, { Component } from 'react';
import { IgnoredSensitiveButton } from './components/SensitiveButton'
import { Heap } from '@heap/react-native-heap';

class MySensitiveScreen extends Component {
  render() {
    const { styles } = this.props.styles;

    return (
      <View style={styles.container}>
        <IgnoredSensitiveButton />
        <IgnoredSensitiveButton />
      </View>
    );
  }
}

export default MySensitiveScreen;

In this case, the button with sensitive data can be re-used throughout the app, but Heap will ignore all interactions with those buttons, due to the use of the HeapIgnore HOC.

For more information on Heap's data privacy options, see How do I use Heap to comply with data privacy legislation?

Troubleshooting

Dynamically linked framework

The react-native-heap module cannot be built as a dynamically linked framework. If your app uses use_frameworks! in your Podfile, you will need to add an additional pre-install step to the Podfile:

pre_install do |installer|
    pod = installer.pod_targets.find { |p| p.name == 'react-native-heap'}
    def pod.static_framework?
        true
    end
end

Expo 49 with Gradle 8

Starting in Expo 49, the ejected Android project uses Gradle 8 by default. If your project includes Heap with Expo autolinking, this can cause Heap to be included twice, which causes a build error with Gradle 8. In that case, you might encounter a build error that references implicitly dependent Gradle tasks that looks like this:

Reason: Task ':heap_react-native-heap:packageDebugResources' uses this output of task ':heap-react-native-heap:compileDebugRenderscript' without declaring an explicit or implicit dependency. 
	This can lead to incorrect results being produced, depending on what order the tasks are executed.
Possible solutions:
	1. Declare task ':heap-react-native-heap:compileDebugRenderscript' as an input of ':heap_react-native-heap:packageDebugResources'.
	2. Declare an explicit dependency on ':heap-react-native-heap:compileDebugRenderscript' from ':heap_react-native-heap:packageDebugResources' using Task#dependsOn.
	3. Declare an explicit dependency on ':heap-react-native-heap:compileDebugRenderscript' from ':heap_react-native-heap:packageDebugResources' using Task#mustRunAfter.

If you see this error, excluding Heap from autolinking with Expo should prevent Heap from being included twice and fix the build error. To do this, add the following to your package.json file:

"expo": {
  "autolinking": {
    "exclude": ["@heap/react-native-heap"]
  }
}

Manual Tracking Usage Examples

The following code snippet is an example of how to set up manual tracking:

// Import Heap.
import Heap from '@heap/react-native-heap';

// Identify your user.
Heap.identify('123456');
Heap.addUserProperties({ name: 'John', age: 54 });

// Add event properties (these persist across sessions).
Heap.addEventProperties({ isLoggedIn: true });

// You can remove a specific property or clear everything.
Heap.removeEventProperty('isLoggedIn');
Heap.clearEventProperties();

// To track an event, use:
Heap.track('signed-up', { isPaid: true, amount: 20 });

Autocaptured Events

Native Events (iOS Only)

In addition to React Native-specific events, the React Native Heap integration support capture of native iOS touch and pageview events. These can be enabled by including enableNativeTouchEventCapture in `heap.config.json:

{
  "default": {
    "enableNativeTouchEventCapture": true
  },
  ...
}

Native Android events, such as fragment transitions, are not currently captured.

Screenviews

Heap captures screenviews through integrations with popular navigation frameworks. Currently, only React Navigation 3.0+ is supported.

Screenviews are not captured by default, and autocapture can be activated as follows:

React Navigation Versions 5 and above

Wrap Heap.withReactNavigationAutotrack() around the NavigationContainer:

const HeapNavigationContainer = Heap.withReactNavigationAutotrack(
  NavigationContainer
);

React Navigation Versions below 5

Wrap the React Navigation AppContainer with Heap.withReactNavigationAutotrack(). For example:

import Heap from '@heap/react-native-heap';

let AppNavigator = createStackNavigator(
  {
    LoginView: { screen: LoginView },
    MainMenu: { screen: MainMenu },
    SearchView: { screen: SearchView },
    ProductView: { screen: ProductView }
  },
  {
    initialRouteName: 'LoginView'
  }
)
let App = Heap.withReactNavigationAutotrack(createAppContainer(AppNavigator));

React Native-Specific Captured Properties:

  • Type: For React Navigation, the event type will be reactNavigationScreenview.
  • path: The path property will contain the the name of the view as defined with React Navigation. Example: LoginView.
  • type: The type of screenview – for standard navigation operations, this will be Navigation/NAVIGATE. This property will not be defined on events if the screen was viewed as the initial route.

Touchables

Heap will capture touches on any component that is backed by any of the Touchable components, like TouchableHighlight, TouchableOpacity, etc. This will effectively capture touch interactions with a wide variety of components, but will not distinguish between gestures on those components. Heap captures the component hierarchy as a part of this event, which should enable basic specification of events.

React Native-specific captured properties:

  • Type: For Touchables, the event type will be TouchableHandlePress.
  • touchableHierarchy: This contains the component hierarchy of associated with the event. Example:
AppContainer;|App;|Provider;|Connect(RouterComponent);|RouterComponent;|Router;|App;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|NavigationContainer;|Navigator;|_default;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|Wrapped;|Connect(ProductScreen);|ProductScreen;|KeyboardView;|KeyboardAvoidingView;|ScrollView;|Button;[title=Save];|TouchableOpacity;|
  • touchState: Touch state of the component at the time of the event. This is a string representation of an internal state enumeration. Example: RESPONDER_ACTIVE_PRESS_IN
  • targetText: If there is a textual element to the component being touched, Heap will capture that associated text. Heap attempts to infer the target text, but may not always be able to extract it automatically. Currently, the text must be a descendent of the Touchable component.

Component Props

Heap attempts to autocapture commonly used props for standard components, as well as popular component libraries and UI kits. For example, most React Native apps will have a component called Button, which often has a prop called title. When a Touchable event is triggered from a Button, the title prop will be automatically included in the event by augmenting the touchbleHierarchy event property.

For example, a Button component labeled with the text β€œSave” would result in a touchableHierarchy event prop like this (note Button;[title=Save];):

AppContainer;|App;|Provider;|Connect(RouterComponent);|RouterComponent;|Router;|App;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|NavigationContainer;|Navigator;|_default;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|Wrapped;|Connect(ProductScreen);|ProductScreen;|KeyboardView;|KeyboardAvoidingView;|ScrollView;|Button;[title=Save];|TouchableOpacity;|

If an event doesn't have sufficient specificity between autocaptured props and component hierarchies, it may be necessary to configure the prop capture further in the app code. For more details, see the Configuring Prop Capture section just below this one.

Configuring Prop Capture

Heap comes configured with an initial set of prop-capture configurations for commonly-used components, and will eventually support common props for popular UI kits like NativeBase and React Native Elements. If capture of a non-default property is required, this can be achieved by adding a small piece of configuration code to the app. Prop capture configurations can be assigned to both stateless functional and stateful components.

To achieve this, Heap will check for a specially-named heapOptions property that contains a set of properties to include or exclude. The structure of the property is as follows:

heapOptions = {
  eventProps: {
    include: [ '<inc_prop_1>', '<inc_prop_2>', ..., '<inc_prop_n>' ],
    exclude: [ '<exc_prop_1>', '<exc_prop_2>', ..., '<exc_prop_n>' ],
  }
}

Properties in the include list will be included in the event. Properties in the exclude list will be explicitly excluded if they would have been otherwise included by either the include list or a built-in prop configuration.

Stateless, Functional Component Example:

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

const ProductItem = ({name, price, onPress}) => {
  return (
      <TouchableOpacity onPress={onPress}>
        <View>
          <Text>{name}</Text>
          <Text>{price}</Text>
        </View>
      </TouchableOpacity>
  );
}

ProductItem.heapOptions = {
  eventProps : { include: [ 'name', 'price' ] }
};

export { ProductItem };

Stateful Component Example

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

export default class ProductItem extends Component {
  heapOptions = {
    eventProps : { include: [ 'name', 'price' ] }
  };    

  render() {
    return (
      <TouchableOpacity onPress={onPress}>
        <View>
          <Text>{name}</Text>
          <Text>{price}</Text>
        </View>
      </TouchableOpacity>
    );
  }
}

Use of a custom prop configuration will result in additional props being captured within the touchableHierarchy event property (note ProductItem;[name=Foo];[price=$1.00]):

AppContainer;|App;|Provider;|Connect(RouterComponent);|RouterComponent;|Router;|App;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|NavigationContainer;|Navigator;|_default;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|NavigationContainer;|KeyboardAwareNavigator;|Navigator;|StackView;|Transitioner;|withOrientation;|StackViewLayout;|ScreenContainer;|Container;|Card;|Screen;|AnimatedComponent;|SceneView;|Wrapped;|Connect(ProductsScreen);|ProductsScreen;|FlatList;|VirtualizedList;|ScrollView;|CellRenderer;|ProductItem;[name=Foo];[price=$1.00];|TouchableOpacity;|

Capturing Properties from Nested Objects and Arrays

Prop capture configs also support capture of props from within more complex objects. For example, if the ProductItem component utilized an item prop instead of name and price, we could capture the props like this:

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

const ProductItem = ({item, onPress}) => {
  return (
      <TouchableOpacity onPress={onPress}>
        <View>
          <Text>{item.name}</Text>
          <Text>{item.price}</Text>
        </View>
      </TouchableOpacity>
  );
}

ProductItem.heapOptions = {
  eventProps : { include: [ 'item.name', 'item.price' ] }
};

export { ProductItem };

Best Practices for Event Definition

For information on capturing and managing data in React Native, see the following:

Changelog

For notable updates to the Heap React library, see the React Changelog.