Invalid prop data-sentry-element supplied to React.Fragment
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
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.
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.
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 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.
+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',
},
})
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? 🙇
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",
},
});
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 🙏