-
-
Notifications
You must be signed in to change notification settings - Fork 615
Description
When using the experimental native bottom tabs API (imported from @react-navigation/bottom-tabs/unstable as documented here) with platform-specific icon configurations, the app crashes with an icon format error. The BottomTabsScreen component only accepts iOS-style icon props (sfSymbolName, imageSource, templateSource) and throws an error when provided with platform-specific icon objects that include ios, android, or shared properties.
Note: I understand this navigator is currently experimental and the API may change in future releases. I'm posting this issue along with a working patch to help others who encounter the same problem while this feature is being developed.
Error message:
[RNScreens] Incorrect icon format. You must provide sfSymbolName, imageSource or templateSource.
What I expected:
Based on the React Navigation documentation, the bottom tabs should support platform-specific icons using Platform.select:
tabBarIcon: Platform.select({
ios: {
type: "sfSymbol",
name: "heart",
},
android: {
type: "drawableResource",
name: "heart_icon",
},
});Or unified platform objects:
tabBarIcon: {
ios: { type: 'sfSymbol', name: 'house.fill' },
android: { type: 'drawableResource', name: 'ic_home' },
shared: { type: 'image', source: require('./home.png') }
}What happens instead:
The app throws the error above because parseIconToNativeProps in BottomTabsScreen.tsx doesn't recognize the platform-specific icon structure and attempts to access sfSymbolName, imageSource, or templateSource directly on the wrapper object.
Steps to reproduce
- Clone the reproduction repository: https://github.com/bilal1031/rnscreens-example-app.git
- Install dependencies:
cd rnscreens-example-app
yarn install- Start the app:
yarn start- Run on iOS or Android simulator
- The error appears immediately when the tabs try to render
Environment
| Package | Version |
|---|---|
| react-native-screens | ~4.16.0 |
| react-native | 0.81.5 |
| expo | ~54.0.27 |
| @react-navigation/bottom-tabs | ^7.8.11 |
| @react-navigation/native | ^7.1.24 |
Platform: iOS Simulator (iPhone 17 Pro Max), also reproduces on Android
Working patch
I have created a working patch that fixes this issue. The patch is available in the reproduction repository and can be applied using patch-package.
Patch contents:
Click to expand patch file
diff --git a/node_modules/react-native-screens/src/components/bottom-tabs/BottomTabsScreen.tsx b/node_modules/react-native-screens/src/components/bottom-tabs/BottomTabsScreen.tsx
index ce02d94..22f29f9 100644
--- a/node_modules/react-native-screens/src/components/bottom-tabs/BottomTabsScreen.tsx
+++ b/node_modules/react-native-screens/src/components/bottom-tabs/BottomTabsScreen.tsx
@@ -4,6 +4,7 @@ import React from 'react';
import { Freeze } from 'react-freeze';
import {
Image,
+ Platform,
StyleSheet,
findNodeHandle,
processColor,
@@ -17,6 +18,8 @@ import BottomTabsScreenNativeComponent, {
type Appearance,
type ItemAppearance,
type ItemStateAppearance,
+ type IconProps,
+ type PlatformIcon,
} from '../../fabric/bottom-tabs/BottomTabsScreenNativeComponent';
import { featureFlags } from '../../flags';
import type {
@@ -241,7 +244,7 @@ function shouldFreezeScreen(
return !nativeViewVisible;
}
-function parseIconToNativeProps(icon: Icon | undefined): {
+function parseIconToNativeProps(icon: Icon | IconProps | undefined): {
iconType?: IconType;
iconImageSource?: ImageSourcePropType;
iconSfSymbolName?: string;
@@ -250,23 +253,55 @@ function parseIconToNativeProps(icon: Icon | undefined): {
return {};
}
- if ('sfSymbolName' in icon) {
- // iOS-specific: SFSymbol usage
+ // Check if it's a PlatformIcon (has platform-specific properties)
+ const isPlatformIcon =
+ typeof icon === 'object' &&
+ ('ios' in icon || 'android' in icon || 'shared' in icon);
+ let platformIcon: PlatformIcon | Icon | undefined = undefined;
+
+ if (isPlatformIcon) {
+ // Handle PlatformIcon with platform-specific logic
+ const platformIconTyped = icon as IconProps;
+
+ if (Platform.OS === 'ios') {
+ platformIcon = platformIconTyped.ios ?? platformIconTyped.shared;
+ } else if (Platform.OS === 'android') {
+ platformIcon = platformIconTyped.android ?? platformIconTyped.shared;
+ } else {
+ platformIcon = platformIconTyped.shared;
+ }
+ } else {
+ // Handle Icon type directly
+ platformIcon = icon as PlatformIcon;
+ }
+
+ if (!platformIcon) {
+ return {};
+ }
+
+ if ('sfSymbolName' in platformIcon) {
return {
iconType: 'sfSymbol',
- iconSfSymbolName: icon.sfSymbolName,
+ iconSfSymbolName: platformIcon.sfSymbolName as string,
};
- } else if ('imageSource' in icon) {
+ } else if ('type' in platformIcon && platformIcon.type === 'sfSymbol') {
+ return {
+ iconType: 'sfSymbol',
+ iconSfSymbolName: platformIcon.name,
+ };
+ } else if ('imageSource' in platformIcon) {
return {
iconType: 'image',
- iconImageSource: icon.imageSource,
+ iconImageSource: platformIcon.imageSource as ImageSourcePropType,
};
- } else if ('templateSource' in icon) {
- // iOS-specifig: image as a template usage
+ } else if ('templateSource' in platformIcon) {
return {
iconType: 'template',
- iconImageSource: icon.templateSource,
+ iconImageSource: platformIcon.templateSource as ImageSourcePropType,
};
+ } else if ('type' in platformIcon && platformIcon.type === 'drawableResource') {
+ // Android drawableResource is handled separately through iconResource/iconResourceName
+ return {};
} else {
// iOS-specific: SFSymbol, image as a template usage
throw new Error(
@@ -285,6 +320,7 @@ function parseIconsToNativeProps(
selectedIconImageSource?: ImageSourcePropType;
selectedIconSfSymbolName?: string;
} {
+
const { iconImageSource, iconSfSymbolName, iconType } =
parseIconToNativeProps(icon);
const {
diff --git a/node_modules/react-native-screens/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts b/node_modules/react-native-screens/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts
index 2bc5724..25fa672 100644
--- a/node_modules/react-native-screens/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts
+++ b/node_modules/react-native-screens/src/fabric/bottom-tabs/BottomTabsScreenNativeComponent.ts
@@ -20,6 +20,38 @@ import { UnsafeMixed } from './codegenUtils';
// iOS-specific: SFSymbol, image as a template usage
export type IconType = 'image' | 'template' | 'sfSymbol';
+export type SFSIconProps = {
+ type: 'sfSymbol';
+ name: string;
+};
+
+export type TemplateIconProps = {
+ type: 'templateSource';
+ templateSource: ImageSource;
+};
+
+export type ImageIconProps = {
+ type: 'imageSource';
+ imageSource: ImageSource;
+};
+
+export type DrawableIconProps = {
+ type: 'drawableResource';
+ name: string;
+};
+
+export type PlatformIcon =
+ | SFSIconProps
+ | TemplateIconProps
+ | ImageIconProps
+ | DrawableIconProps;
+
+export type IconProps = {
+ ios?: SFSIconProps | TemplateIconProps;
+ android?: DrawableIconProps;
+ shared?: ImageIconProps;
+};
+
// eslint-disable-next-line @typescript-eslint/ban-types
type GenericEmptyEvent = Readonly<{}>;Screens version
~4.16.0
React Native version
0.81.5
Platforms
iOS
JavaScript runtime
None
Workflow
Expo managed workflow
Architecture
Fabric (New Architecture)
Build type
None
Device
iOS simulator
Device model
No response
Acknowledgements
Yes