sentry-react-native icon indicating copy to clipboard operation
sentry-react-native copied to clipboard

Invalid prop data-sentry-element supplied to React.Fragment

Open mitaxe opened this issue 7 months ago • 8 comments

What React Native libraries do you use?

Hermes, React Navigation, React Native without Frameworks, RN New Architecture. I am testing only on Android

Are you using sentry.io or on-premise?

sentry.io (SaS)

@sentry/react-native SDK Version

6.10.0

How does your development environment look like?

⬇  Place the `npx react-native@latest info` output here. ⬇ 

ystem:
  OS: macOS 15.5
  CPU: (8) arm64 Apple M2
  Memory: 117.84 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.14.0
    path: ~/.nvm/versions/node/v22.14.0/bin/node
  Yarn:
    version: 1.22.19
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.9.2
    path: ~/.nvm/versions/node/v22.14.0/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/bogdannosovytskyy/.rvm/gems/ruby-3.2.2/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.2
      - iOS 18.2
      - macOS 15.2
      - tvOS 18.2
      - visionOS 2.2
      - watchOS 11.2
  Android SDK:
    API Levels:
      - "31"
      - "33"
      - "34"
      - "35"
    Build Tools:
      - 27.0.3
      - 30.0.3
      - 31.0.0
      - 33.0.0
      - 33.0.1
      - 34.0.0
      - 34.0.0
      - 35.0.0
    System Images:
      - android-31 | Google Play ARM 64 v8a
      - android-33 | Google APIs ARM 64 v8a
      - android-34 | Google APIs ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2024.3 AI-243.25659.59.2432.13423653
  Xcode:
    version: 16.2/16C5032a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.10
    path: /usr/bin/javac
  Ruby:
    version: 3.2.2
    path: /Users/bogdannosovytskyy/.rvm/rubies/ruby-3.2.2/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: false
  newArchEnabled: false


Sentry.init()

 Sentry.init({
      dsn: Config.SENTRY_DSN,
      profilesSampleRate: 1.0,

      // Attach screenshots to errors
      attachScreenshot: true,

      // Environment and release tracking
      environment: Config.ENV,
      release: `${getVersion()}-${getBuildNumber()}`,
      dist: getBuildNumber(),

      // Session tracking
      enableAutoSessionTracking: true,
      sessionTrackingIntervalMillis: 30000,

      // Tracing
      enableUserInteractionTracing: false,
      tracesSampleRate: 0,

      // Error tracking
      attachStacktrace: true,
      maxBreadcrumbs: 100,
      enableNativeCrashHandling: true,
      enableNativeNagger: true,

      // Replays
      replaysSessionSampleRate: isDebug() ? 1 : 0.1,
      replaysOnErrorSampleRate: 1.0,

      integrations: [
        Sentry.reactNavigationIntegration(),
        Sentry.mobileReplayIntegration({
          maskAllText: true,
          maskAllImages: false,
          maskAllVectors: false,
        }),
      ],

      // Enable in development if needed
      debug: false,

      // Enable debug mode in development
      enabled: !isDebug(),

      beforeSend(event) {
        // Filter out certain errors
        if (event.exception) {
          const error = event.exception.values?.[0]
          // Don't send network errors
          if (error?.type === 'NetworkError') {
            return null
          }
        }
        return event
      },
    })

Steps to Reproduce

The console error displays every time when I am using <Fragment> with annotateReactComponents option settled in the metro config file.

I didn't have this console error when used RN 0.72.8, it appeared once I upgraded the app to RN 0.79.2 and enabled hermes + fabric

Expected Result

The Fragment must be ignored by default, or it should be possible to ignore fragment

This doesn't fix the issue

  return withSentryConfig(mergedConfig, {
    annotateReactComponents: {
      ignoredComponents: [
        'Fragment',
        'React.Fragment',
      ],
    },

Actual Result

Image

mitaxe avatar Jun 06 '25 20:06 mitaxe

Hi @mitaxe,
thank you for the message,

Fragments should be already ignored, for context here is the implementation of the annotation plugin.

Could you share with us an example of the app causing the issue or the JSX code? This would help a lot speeding up the investigation of the issue.

krystofwoldrich avatar Jun 10 '25 09:06 krystofwoldrich

Hi @krystofwoldrich,

Thank you for your reply.

I can provide one area where the issue appears for sure.

import React, { FC, Fragment } from 'react'
import { Dimensions, StyleSheet, View } from 'react-native'

import { toastConfig } from '@app/components/Toast/toastConfig'
import useSendPopupEvent from '@app/hooks/analytics/useSendPopupEvent'
import { colors } from '@app/styles'
import Modal from 'react-native-modal'
import { Portal } from 'react-native-paper'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import Toast from 'react-native-toast-message'

import FocusAwareStatusBar from '../FocusAwareStatusBar'
import ModalBottomCloseButton from './components/ModalBottomCloseButton'
import ModalHeader from './components/ModalHeader'
import { MODAL_TRANSITION_TIMING } from './constants'
import { ModalTestID, SkyfiModalProps } from './types'

const MyModal: FC<SkyfiModalProps> = (props) => {
  const {
    children,
    onClose,
    isVisible,
    backdropOpacity = 0.5,
    animationIn = 'slideInUp',
    animationOut = 'slideOutDown',
    backdropTransitionOutTiming = 0,
    animationInTiming = MODAL_TRANSITION_TIMING,
    useNativeDriver = true,
    statusBarTranslucent = true,
    hideModalContentWhileAnimating = true,
    hasBackdrop = true,
    avoidKeyboard = false,
    header,
    style,
    containerStyle,
    popupName,
    screenName,
    showClose = false,
    isFullscreen = false,
    testID = ModalTestID.Container,
    showBottomClose = false,
    closeButtonStyle,
    closeButtonColor,
    closeButtonSize,
    analyticsProps,
    withPortal = false,
    closeOnBackdropPress = true,
    ...restOfModalProps
  } = props

  useSendPopupEvent({
    isVisible,
    analyticsProps,
    popupName,
    screenName,
  })

  const { top } = useSafeAreaInsets()

  const modalStyle = [styles.modal, isFullscreen && styles.fullscreenModal, style]

  const PortalWrapper = withPortal ? Portal.Host : Fragment

  const { height: screenHeight } = Dimensions.get('screen')

  return (
    <Modal
      {...{
        deviceHeight: screenHeight,
        backdropTransitionOutTiming,
        useNativeDriver,
        backdropOpacity,
        hasBackdrop,
        avoidKeyboard,
        animationIn,
        hideModalContentWhileAnimating,
        animationOut,
        isVisible,
        animationInTiming,
        statusBarTranslucent,
        style: modalStyle,
        onBackdropPress: closeOnBackdropPress ? onClose : undefined,
        ...(isFullscreen && {
          containerStyle: styles.fullScreenModalDefaultContainer,
        }),

        ...restOfModalProps,
      }}
      testID={testID}
    >
      <PortalWrapper>
        <View
          style={[
            styles.container,
            isFullscreen && styles.fullscreenModalContainer,
            containerStyle,
            isFullscreen && { paddingTop: top },
          ]}
        >
          <FocusAwareStatusBar animated {...(isFullscreen && { barStyle: 'dark-content' })} />
          <ModalHeader
            {...{
              header,
              showClose,
              showBottomClose,
              onClose,
              isFullscreen,
              closeButtonStyle,
              closeButtonColor,
              closeButtonSize,
            }}
          />

          {children}
          {showBottomClose && <ModalBottomCloseButton onClose={onClose} />}
        </View>
        <Toast config={toastConfig} />
      </PortalWrapper>
    </Modal>
  )
}

const PortalWrapper = withPortal ? Portal.Host : Fragment

When PortalWrapper is Fragment - the issue appears.

mitaxe avatar Jun 10 '25 11:06 mitaxe

My Metro config

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
const { withSentryConfig } = require('@sentry/react-native/metro')

module.exports = (async () => {
  // Get the default configuration from the new config format
  const defaultConfig = await getDefaultConfig(__dirname)

  // Your existing custom configuration
  const {
    resolver: { sourceExts, assetExts },
  } = await getDefaultConfig()

  const resolvedSourceExts =
    process.env.APP_MODE === 'mocked' ? ['mock.ts', ...sourceExts] : sourceExts

  const customConfig = {
    transformer: {
      babelTransformerPath: require.resolve('react-native-svg-transformer/react-native'),
      minifierConfig: {
        keep_classnames: true,
        keep_fnames: true,
        mangle: {
          keep_classnames: true,
          keep_fnames: true,
        },
      },
    },
    resolver: {
      assetExts: assetExts.filter((ext) => ext !== 'svg'),
      sourceExts: [...resolvedSourceExts, 'svg'],
    },
  }

  // Merge the default configuration with your custom configuration
  const mergedConfig = mergeConfig(defaultConfig, customConfig)

  // Apply Sentry's Metro configuration
  return withSentryConfig(mergedConfig, { annotateReactComponents: true })
})()

mitaxe avatar Jun 11 '25 17:06 mitaxe

@mitaxe Thank you for the details, I'll try to setup similar condition for the Fragment in our sample and let you know, if that worked out.

krystofwoldrich avatar Jun 12 '25 09:06 krystofwoldrich

+1 facing same issue.

Here’s a minimal repro:

import { Fragment, useEffect } from 'react'
import { Platform, StyleSheet, Text, View } from 'react-native'

import * as Sentry from '@sentry/react-native'
import { Stack, useNavigationContainerRef } from 'expo-router'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { FullWindowOverlay } from 'react-native-screens'

import { setupSentry } from '../logger/sentry'

const isIos = Platform.OS === 'ios'
function Component() {
  const ContainerComponent = isIos ? FullWindowOverlay : Fragment
  return (
    <ContainerComponent>
      <View style={styles.container}>
        <Text style={{ color: '#f2f2f2', fontSize: 24 }}>Hello world!</Text>
      </View>
    </ContainerComponent>
  )
}

const { registerNavigation } = setupSentry()

function App() {
  const navigationContainerRef = useNavigationContainerRef()

  useEffect(() => {
    if (navigationContainerRef) {
      registerNavigation(navigationContainerRef)
    }
  }, [navigationContainerRef])

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Stack>
        <Stack.Screen
          name='index'
          options={{
            headerShown: false,
          }}
        />
        <Stack.Screen
          name='second'
          options={{
            headerShown: false,
          }}
        />
        <Stack.Screen
          name='third'
          options={{
            headerShown: false,
          }}
        />
      </Stack>
      <Component />
    </GestureHandlerRootView>
  )
}

export default Sentry.wrap(App)

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-start',
    alignItems: 'center',
    padding: 100,
    backgroundColor: '#4B4E52',
  },
})

metehandemir avatar Jun 21 '25 19:06 metehandemir

Thank you all for reporting and providing reproduction code 🙇 We were able to reproduce the issue.

The issue occurs because the babel plugin doesn't recognize that a variable (like const Wrapper = Fragment) resolves to React.Fragment at runtime, so it adds invalid data-sentry-* props to what becomes a Fragment element, causing React to throw an error since Fragment only accepts key and children props. We will iterate with a fix on this.

A workaround that worked for me was to add the "wrapper component" (PortalWrapper or ContainerComponent in the provided code) in the ignoredComponents like below:

const sentryConfig = withSentryConfig(mergedConfig, {
  annotateReactComponents: {
    ignoredComponents: ['WrapperComponent'],
  },
});

Let us know if that helped? 🙇

antonis avatar Jun 24 '25 11:06 antonis

Ignoring the wrapper component worked, thank you! @antonis

However, I wanted to highlight another case where this issue still occurs: when using platform-specific component resolution via file extensions. For example:

── components ├── FullWindowOverlay.ios.js ├── FullWindowOverlay.js

FullWindowOverlay.ios.js

export { FullWindowOverlay } from 'react-native-screens'

FullWindowOverlay.js

export { Fragment as FullWindowOverlay } from 'react'

When using FullWindowOverlay in this setup on Android, Sentry still tries to inject props into Fragment, which throws the same “invalid prop” warning. Just wanted to share this in case others hit the same problem with conditional exports.

App.js

import { useEffect } from "react";
import { Platform, StyleSheet, Text, View } from "react-native";

import * as Sentry from "@sentry/react-native";
import { Stack, useNavigationContainerRef } from "expo-router";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { FullWindowOverlay } from "/components/FullWindowOverlay";

import { setupSentry } from "../logger/sentry";

function Component() {
  return (
    <FullWindowOverlay>
      <View style={styles.container}>
        <Text style={{ color: "#f2f2f2", fontSize: 24 }}>Hello world!</Text>
      </View>
    </FullWindowOverlay>
  );
}

const { registerNavigation } = setupSentry();

function App() {
  const navigationContainerRef = useNavigationContainerRef();

  useEffect(() => {
    if (navigationContainerRef) {
      registerNavigation(navigationContainerRef);
    }
  }, [navigationContainerRef]);

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Stack>
        <Stack.Screen
          name="index"
          options={{
            headerShown: false,
          }}
        />
        <Stack.Screen
          name="second"
          options={{
            headerShown: false,
          }}
        />
        <Stack.Screen
          name="third"
          options={{
            headerShown: false,
          }}
        />
      </Stack>
      <Component />
    </GestureHandlerRootView>
  );
}

export default Sentry.wrap(App);

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "flex-start",
    alignItems: "center",
    padding: 100,
    backgroundColor: "#4B4E52",
  },
});

metehandemir avatar Jun 24 '25 23:06 metehandemir

When using FullWindowOverlay in this setup on Android, Sentry still tries to inject props into Fragment, which throws the same “invalid prop” warning. Just wanted to share this in case others hit the same problem with conditional exports.

Thank you for sharing your finding @metehandemir 🙇 The workaround in this case should prevent the error on Android but has the undesired side effect of ignoring the iOS component too.

const sentryConfig = withSentryConfig(mergedConfig, {
  annotateReactComponents: {
    ignoredComponents: [
      'FullWindowOverlay',
    ],
  },
});

We would work on a proper fix for the cases shared where a Fragment ends up being used through an alias or indirection and iterate back 🙏

antonis avatar Jun 25 '25 06:06 antonis