> ## Documentation Index
> Fetch the complete documentation index at: https://launchdarkly.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Observability to your React Native app in 5 minutes

export const BlogAuthor = ({date, avatar, alt, name}) => {
  return <div className="-mb-4">
      <p className="m-0 text-base font-semibold">
        <em>Published {date}</em>
      </p>
      <div className="flex items-center -mt-4">
        {avatar && <img src={avatar} alt={alt} className="h-8 w-8 rounded-full m-0" />}
        <p className="ml-[5px] text-lg">{name}</p>
      </div>
    </div>;
};

<View title="Developer" />

<View title="Federal docs" />

<View title="EU docs" />

<BlogAuthor date="September 25, 2025" avatar="/images/authors/alexis-roberson.png" alt="portrait of Alexis Roberson." name="by Alexis Roberson" />

In modern application development, feature flags are the guardrails that keep experiments controlled and rollbacks safe when conditions shift. If feature flags act as the guardrails, observability provides the visibility: the headlights (traces), mirrors (logs), and dashboard instruments (metrics) that reveal what’s happening in the environment and how well a feature is performing. Together, feature flags and observability unlock powerful insights by correlating code changes with real-time system behavior. This combination reduces time-to-diagnosis and builds greater confidence when rolling out new features.

In this post, we’ll walk through just how to add observability to a React Native application using LaunchDarkly’s observability SDK. To demonstrate the process, we’ll build on the PlusOne app, a simple counter app that includes increment (+1), reset, and error-triggering buttons. This lightweight demo provides a clean foundation to showcase how logs, traces, and errors can seamlessly flow into LaunchDarkly for monitoring and debugging.

<Frame caption="Screenshot final result of PlusOne app.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/plusone.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=50dbe379568185895d3332fdfb68f5c1" alt="Final result of PlusOne app." width="694" height="1374" data-path="images/tutorials/ollsdk-react-native/plusone.png" />
</Frame>

## Prerequisites

* LaunchDarkly account. [Sign up for a free one here](https://app.launchdarkly.com/signup).
* Visual Studio or another code editor of choice.

All code from this tutorial can be found [on GitHub](https://github.com/arober39/PlusOne).

## Setting up your environment

Before running a React Native app, make sure your development environment is set up correctly. [You can find the full setup instructions for both Android and iOS here](https://reactnative.dev/docs/set-up-your-environment?platform=android#cocoapods).

In this tutorial, we'll be running iOS, but keep in mind Expo Orbit, the platform we'll be using to run our iOS simulator, requires both Xcode and Android Studio to be installed.

After going through the instructions you should have the following installed:

* Node JS (preferably via [nvm](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide/))
* Watchman for file monitoring
* JDK via Zulu package manager.
* [Android Studio](https://developer.android.com/studio/index.html). Don’t forget to set your Android\_Home environment variables.
* [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) for the iOS simulator.
* [CocoaPods](https://cocoapods.org/) for iOS dependency management.
* [Expo orbit](https://expo.dev/orbit) for running expo apps Android or iOS.

If you're using Android, don't forget to add your environment variables to bash or zsh profile.

```bash title="Android vars" for="android" lines wrap theme={null}
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
```

## Starting up the PlusOne app

To get started, let’s clone the [repo](https://github.com/arober39/PlusOne#) for the PlusOne app and run `npm install` to ensure the proper dependencies are present in our node\_modules file.

```bash title="Cloning repo" for="git" lines wrap theme={null}
git clone https://github.com/arober39/PlusOne
```

```bash title="Install using npm" for="npm" lines wrap theme={null}
cd PlusOne
npm install
```

We’ll also need to run both the prebuild command to generate the ios file and the expo run command to run the iOS simulator.

```bash title="Prebuild for iOS" for="npx" lines wrap theme={null}
npx expo prebuild
```

```bash title="Run expo app" for="npm" lines wrap theme={null}
npm expo run:ios
```

Now we can view the iOS app in the iPhone simulator using npm.

```bash title="Install using npm" for="npm" lines wrap theme={null}
# iOS
npm run ios

# Android
npm run android
```

The app should look something like this:

<Frame caption="Final result of PlusOne app.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/plusone.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=50dbe379568185895d3332fdfb68f5c1" alt="Final result of PlusOne app" width="694" height="1374" data-path="images/tutorials/ollsdk-react-native/plusone.png" />
</Frame>

Feel free to interact with the app to ensure all is working as expected.

As you can see in the code, we have three buttons: one that adds one to the displayed number, one to bring the count back to zero and an intentional Error button to test error monitoring within the LaunchDarkly UI.

```javascript expandable lines wrap theme={null}
// app/index.tsx

import { useState } from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";

export default function Index() {
 const [count, setCount] = useState(0);

 const handleReset = () => setCount(0);
 const handleIncrement = () => setCount((prev) => prev + 1);

 const triggerRecordedError = () => {
   try {
     throw new Error("Simulated controlled error from Plus One app")
   } catch (e) {
     alert("You intentionally threw an error")
   }
 };

 return (
   <View style={styles.container}>
     <View style={styles.header}>
       <Text style={styles.headerText}>Plus One</Text>
     </View>
     <View style={styles.counterWrapper}>
       <Text style={styles.counterText}>{count}</Text>
     </View>
     <View style={styles.actionsRow}>
       <ButtonBox label="Reset" onPress={handleReset} />
       <ButtonBox label="+1" onPress={handleIncrement} />
       <ButtonBox label="Error" onPress={triggerRecordedError} />
     </View>
   </View>
 );
}

type ButtonBoxProps = {
 label: string;
 onPress: () => void;
};

function ButtonBox({ label, onPress }: ButtonBoxProps) {
 return (
   <TouchableOpacity onPress={onPress} style={styles.button} activeOpacity={0.8}>
     <Text style={styles.buttonText}>{label}</Text>
   </TouchableOpacity>
 );
}


/* The rest of the application code */ 

```

Now that we have verified a working app, we can add observability support by downloading the observability React Native [SDK](/docs/sdk/observability/react-native).

```bash title="Install using npm" for="npm" lines wrap theme={null}
npm install @launchdarkly/react-native-client-sdk
npm install @launchdarkly/observability-react-native
```

Next, you’ll need to initialize the React Native LaunchDarkly client in the `_layout` file. Replace the in the layout file by pasting the following code.

```javascript expandable lines wrap theme={null}
// app/_layout.tsx

import { Observability } from '@launchdarkly/observability-react-native';
import { AutoEnvAttributes, LDOptions, LDProvider, ReactNativeLDClient } from '@launchdarkly/react-native-client-sdk';
import { Stack } from 'expo-router';
import { useEffect, useState } from 'react';

const options: LDOptions = {
 applicationInfo: {
   id: 'Plus-One',
   name: 'Sample Application',
   version: '1.0.0',
   versionName: 'v1',
 },
 debug: true,
 plugins: [
   new Observability({
     serviceName: 'my-react-native-app',
     serviceVersion: '1.0.0',
   })
 ],
};

const userContext = { kind: 'user', key: 'test-hello' };

export default function RootLayout() {
 const [client, setClient] = useState<ReactNativeLDClient | null>(null);
  useEffect(() => {
   // Initialize client
   const featureClient = new ReactNativeLDClient(
     'mob-abc123',
     AutoEnvAttributes.Enabled,
     options,
   );

   featureClient.identify(userContext).catch((e: any) => console.log(e));
  
   setClient(featureClient);
  
   // Cleanup function that runs when component unmounts
   return () => {
     featureClient.close();
   };
 }, []);

 if (!client) {
   return null;
 }
 return (
   <LDProvider client={client}>
     <Stack />
   </LDProvider>
 );
}
```

First, we’re importing the Observability SDK as well as a few LaunchDarkly libraries to add options and attributes to the LaunchDarkly client.- Initialized the SDK and [plugin options](https://launchdarkly.github.io/observability-sdk/sdk/@launchdarkly/observability-react-native/interfaces/ReactNativeOptions.html).

* Defined the user [context](/docs/home/flags/contexts).
* Lastly, you initialized the client.

Now that you have defined your LaunchDarkly React Native client, you can implement different observability methods within your application logic.

We can do this by importing the LDObserve library in the app/\_layout.tsx file.

```javascript lines wrap theme={null}
import { LDObserve } from '@launchdarkly/observability-react-native';
```

Then, add the recordError() method within the triggerRecordedError function inside the app/\_layout.tsx file. This will allow for error messages to be sent back to the LaunchDarkly UI.

```javascript lines wrap theme={null}
 const triggerRecordedError = () => {
   try {
     throw new Error("Simulated controlled error from Plus One app")
   } catch (e) {
     LDObserve.recordError(e as Error, {feature: "test-button"})
     alert("You intentionally threw an error")
   }
 };

```

Before being able to receive data in the LaunchDarkly UI, you’ll need to add your mobile key to the React Native LaunchDarkly client, which can be found by logging in to the LaunchDarkly UI.

<Frame caption="Screenshot of Sign in page.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/LD_signin.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=e56245e595c0fe8adce7938b0f9ce85b" alt="The LaunchDarkly sign in page" width="1876" height="1072" data-path="images/tutorials/ollsdk-react-native/LD_signin.png" />
</Frame>

Once logged in, tap the settings button at the bottom left.

<Frame caption="Screenshot of landing page after sign in.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/LD_onboardingpage.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=2e943c749261a44bd0c1ac33f0820b79" alt="The LaunchDarkly onboarding page" width="1916" height="932" data-path="images/tutorials/ollsdk-react-native/LD_onboardingpage.png" />
</Frame>

Navigate to the Projects page and click create to create a new project.

<Frame caption="Screenshot of Project page.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/LD_projectpage.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=ba36b79c37801e4aab68ec3051052021" alt="Screenshot of project landing page" width="1913" height="931" data-path="images/tutorials/ollsdk-react-native/LD_projectpage.png" />
</Frame>

Define the new Project and click Create Project.

<Frame caption="Screenshot of New project widget page.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/createnewproject.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=6e9e0bb9e5fc2391c5446fd1b15ca365" alt="Screenshot of New project page" width="1916" height="990" data-path="images/tutorials/ollsdk-react-native/createnewproject.png" />
</Frame>

Then, define the environment where you would like your data to be sent.

<Frame caption="Screenshot of page to create new environment.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/createnewenvironement.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=b2533f9e94e32aa763d3155de504156e" alt="Screenshot of create new environment widget page" width="1918" height="992" data-path="images/tutorials/ollsdk-react-native/createnewenvironement.png" />
</Frame>

Now, grab the mobile key by pressing the three dots for the environment and selecting the mobile key, which will copy the key to your keyboard.

<Frame caption="Screenshot of steps to copy mobile key">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/grabmobkey.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=a470430a4475be1b8ea9e1d0a552d4af" alt="Screenshot of where to find mobile key" width="1918" height="992" data-path="images/tutorials/ollsdk-react-native/grabmobkey.png" />
</Frame>

Then, add it to the app/\_layout file.

```javascript lines wrap theme={null}
   const featureClient = new ReactNativeLDClient(
     ‘mob-abc123’,
     AutoEnvAttributes.Enabled,
     options,
   );
```

Finally, you can generate data by interacting with your app in the iOS app simulator.

Feel free to restart the app to ensure data is displaying in real time.

```bash lines wrap theme={null}
npm expo run:ios
```

Once you navigate back to the LaunchDarkly UI, you should be able to see the logs, traces, and errors under the Monitor section.

### Logs

<Frame caption="Screenshot of final logs page.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/logspage.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=9a4181f9a857bf7278b5d7032644d67f" alt="Screenshot of logs page" width="1884" height="1008" data-path="images/tutorials/ollsdk-react-native/logspage.png" />
</Frame>

### Traces

<Frame caption="Screenshot of final traces page.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/tracespage.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=1c5fadf73b8220cc9c39e3eb24be9fde" alt="Screenshot of traces page" width="1878" height="1010" data-path="images/tutorials/ollsdk-react-native/tracespage.png" />
</Frame>

### Errors

<Frame caption="Screenshot of final errors page.">
  <img src="https://mintcdn.com/launchdarkly/hRg11GGWIRyNRMYL/images/tutorials/ollsdk-react-native/errorspage.png?fit=max&auto=format&n=hRg11GGWIRyNRMYL&q=85&s=1eff158017091323fa311839b11fe6f8" alt="Screenshot of errors page" width="1864" height="1010" data-path="images/tutorials/ollsdk-react-native/errorspage.png" />
</Frame>

## Conclusion

In just a few minutes, we’ve taken the PlusOne React Native app from a simple counter to a fully observable application connected to LaunchDarkly. By setting up the SDK, initializing observability plugins, and recording errors, we now have a live feedback loop where application behavior is visible in the LaunchDarkly UI. This makes it far easier to diagnose issues, validate feature flag rollouts, and ensure smooth user experiences.

## Next Steps

Looking ahead, there are many ways to expand on what we’ve built by including features like recording [custom metrics](/docs/sdk/features/observability-metrics) and [session replay](/docs/sdk/features/session-replay-config), which provide even deeper insights into app behavior. By integrating observability at the foundation of your React Native projects, you equip your team with the clarity needed to debug faster, ship features more confidently, and deliver reliable experiences to your users.

You can also read [this article](https://launchdarkly.com/blog/welcome-highlight-to-launchdarkly/) to learn more about observability and guarded releases.
