Repository: getsentry/sentry-react-native (TypeScript)

Issue:
Enabling Sentry.ReactNativeTracing results in Cannot read properties of undefined (reading 'remove') when sending error.
**OS:**
- [x]  Windows
- [x]  Linux

**Platform:**
- [ ]  iOS
- [x]  Android

**SDK:**
- [x]  `@sentry/react-native` (>= 1.0.0)

**SDK version:** 5.19.1

**`react-native` version:** 0.73.6

**Are you using Expo?**
- [x]  Yes

**Are you using sentry.io or on-premise?**
- [x]  sentry.io (SaaS)

**If you are using sentry.io, please post a link to your issue so we can take a look:**
[The issue is logged but breaks the dev server.](https://lifeguides.sentry.io/issues/5085465590/?environment=development&project=4506758892093440&query=is%3Aunresolved&referrer=issue-stream&statsPeriod=14d&stream_index=0)

[Link to issue]

**Configuration:**

(`@sentry/react-native`)
```ts
import * as Sentry from '@sentry/react-native';
import { Slot, useNavigationContainerRef } from 'expo-router';

const routingInstrumentation = new Sentry.ReactNavigationInstrumentation();

Sentry.init({
  dsn: 'My-DSN',
  debug: true,
  tracesSampleRate: 1.0,
  integrations: [
    new Sentry.ReactNativeTracing({
        enableNativeFramesTracking: Constants.appOwnership !== 'expo',
      enableUserInteractionTracing: true,
      routingInstrumentation,
    }),
  ],
});

export function RootLayout() {
  useEffect(() => {
    if (navRef) {
      routingInstrumentation.registerNavigationContainer(navRef);
    }
  }, [navRef]);

   return <Slot />
}



export default Sentry.wrap(RootLayout);
```


---
**I have the following issue:**

When I willfully throw a test error via Sentry, with the above configuration, the dev server is halted with an error emitting from sentry itself. Here is the trace:

```sh
TypeError: Cannot read properties of undefined (reading 'remove')
    at callback (/home/the-simian/<app>/node_modules/@sentry/react-native/dist/js/tracing/transaction.js:28:22)
    at IdleTransaction.end (/home/the-simian/<app>/node_modules/@sentry/core/cjs/tracing/idletransaction.js:160:9)
    at IdleTransaction.finish (/home/the-simian/<app>/node_modules/@sentry/core/cjs/tracing/span.js:475:17)
    at ReactNavigationInstrumentation.apply [as _discardLatestTransaction] (/home/the-simian/<app>/node_modules/@sentry/react-native/dist/js/tracing/reactnavigation.js:174:37)
    at Timeout.sentryWrapped [as _onTimeout] (/home/the-simian/<app>/node_modules/@sentry/browser/cjs/helpers.js:93:17)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)

Node.js v21.6.2
```



**Steps to reproduce:**
- Step 1: Configure Sentry as seen above.
- Step 2: Add a 'Test' Button and Press it like:

```ts
      <Pressable
        onPress={() => {
          Sentry.captureException(
            new Error(
              `[Test Error] | 💥💥 Kaboom!!! 💥💥 at ${new Date().toISOString()}`,
            ),
          );
        }}
      >
        <View>
          <Text>
            Kaboom!
          </Text>
        </View>
      </Pressable>
```

**Actual result:**

See trace

**Expected result:**

No Error



Discussion: I have the same issue.
Hi,
thank you for the message @the-simian and @daehyeonmun2021.

I was able to reproduce this with `react-native-web`. Can you confirm it happens on the web or do you experience it also on Android or iOS with or without Expo Go?



Fix (patch):
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8fe8f27d8..996893e939 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 ### Fixes
 
 - Do not enable NativeFramesTracking when native is not available ([#3705](https://github.com/getsentry/sentry-react-native/pull/3705))
+- Cancel spans in background doesn't crash in environments without AppState ([#3727](https://github.com/getsentry/sentry-react-native/pull/3727))
 
 ### Dependencies
 
diff --git a/src/js/tracing/transaction.ts b/src/js/tracing/transaction.ts
index 2686fe9603..a980f86d95 100644
--- a/src/js/tracing/transaction.ts
+++ b/src/js/tracing/transaction.ts
@@ -1,6 +1,6 @@
 import { type BeforeFinishCallback, type IdleTransaction } from '@sentry/core';
 import { logger } from '@sentry/utils';
-import type { AppStateStatus } from 'react-native';
+import type { AppStateStatus, NativeEventSubscription } from 'react-native';
 import { AppState } from 'react-native';
 
 /**
@@ -22,15 +22,25 @@ export const onlySampleIfChildSpans: BeforeFinishCallback = (transaction: IdleTr
  * Hooks on AppState change to cancel the transaction if the app goes background.
  */
 export const cancelInBackground = (transaction: IdleTransaction): void => {
-  const subscription = AppState.addEventListener('change', (newState: AppStateStatus) => {
-    if (newState === 'background') {
-      logger.debug(`Setting ${transaction.op} transaction to cancelled because the app is in the background.`);
-      transaction.setStatus('cancelled');
-      transaction.finish();
-    }
-  });
-  transaction.registerBeforeFinishCallback(() => {
-    logger.debug(`Removing AppState listener for ${transaction.op} transaction.`);
-    subscription.remove();
-  });
+  if (!AppState || !AppState.isAvailable) {
+    logger.warn('AppState is not available, spans will not be canceled in background.');
+    return;
+  }
+
+  // RN Web can return undefined, https://github.com/necolas/react-native-web/blob/8cf720f0e57c74a254bfa7bed0313e33a4b29c11/packages/react-native-web/src/exports/AppState/index.js#L55
+  const subscription: NativeEventSubscription | undefined = AppState.addEventListener(
+    'change',
+    (newState: AppStateStatus) => {
+      if (newState === 'background') {
+        logger.debug(`Setting ${transaction.op} transaction to cancelled because the app is in the background.`);
+        transaction.setStatus('cancelled');
+        transaction.finish();
+      }
+    },
+  );
+  subscription &&
+    transaction.registerBeforeFinishCallback(() => {
+      logger.debug(`Removing AppState listener for ${transaction.op} transaction.`);
+      subscription && subscription.remove && subscription.remove();
+    });
 };