From e2162cd6844525257c3968dca3d973182388c404 Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Thu, 23 Apr 2026 16:50:17 +0200 Subject: [PATCH 1/3] feat: enable synchronous header config shadow state updates by default Co-Authored-By: Claude Opus 4.6 (1M context) --- FabricExample/App.tsx | 2 +- ios/RNSScreenStackHeaderConfig.mm | 1 + src/fabric/ScreenStackHeaderConfigNativeComponent.ts | 2 +- src/flags.ts | 7 ++++++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/FabricExample/App.tsx b/FabricExample/App.tsx index e5d153146f..1f3c1d79b1 100644 --- a/FabricExample/App.tsx +++ b/FabricExample/App.tsx @@ -2,7 +2,7 @@ import App from '../apps'; import { featureFlags } from 'react-native-screens'; featureFlags.experiment.synchronousScreenUpdatesEnabled = false; -featureFlags.experiment.synchronousHeaderConfigUpdatesEnabled = false; +featureFlags.experiment.synchronousHeaderConfigUpdatesEnabled = true; featureFlags.experiment.synchronousHeaderSubviewUpdatesEnabled = false; featureFlags.experiment.androidResetScreenShadowStateOnOrientationChangeEnabled = true; diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm index 5970e3e3a6..b42d5df2d9 100644 --- a/ios/RNSScreenStackHeaderConfig.mm +++ b/ios/RNSScreenStackHeaderConfig.mm @@ -70,6 +70,7 @@ - (instancetype)initWithFrame:(CGRect)frame _show = YES; _translucent = NO; _addedReactSubviewsInCurrentTransaction = false; + _synchronousShadowStateUpdatesEnabled = YES; _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}); [self initProps]; } diff --git a/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/src/fabric/ScreenStackHeaderConfigNativeComponent.ts index ba2479de0f..66388ce8cb 100644 --- a/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +++ b/src/fabric/ScreenStackHeaderConfigNativeComponent.ts @@ -80,7 +80,7 @@ export interface NativeProps extends ViewProps { onPressHeaderBarButtonMenuItem?: | CT.DirectEventHandler | undefined; - synchronousShadowStateUpdatesEnabled?: CT.WithDefault; + synchronousShadowStateUpdatesEnabled?: CT.WithDefault; // Experimental userInterfaceStyle?: CT.WithDefault; diff --git a/src/flags.ts b/src/flags.ts index 731b70c141..7f284ad248 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -1,5 +1,5 @@ const RNS_SYNCHRONOUS_SCREEN_STATE_UPDATES_DEFAULT = false; -const RNS_SYNCHRONOUS_HEADER_CONFIG_STATE_UPDATES_DEFAULT = false; +const RNS_SYNCHRONOUS_HEADER_CONFIG_STATE_UPDATES_DEFAULT = true; const RNS_SYNCHRONOUS_HEADER_SUBVIEW_STATE_UPDATES_DEFAULT = false; const RNS_ANDROID_LEGACY_TOP_INSET_BEHAVIOR_DEFAULT = false; const RNS_ANDROID_RESET_SCREEN_SHADOW_STATE_ON_ORIENTATION_CHANGE_DEFAULT = @@ -201,6 +201,11 @@ export const featureFlags = { set synchronousScreenUpdatesEnabled(value: boolean) { synchronousScreenUpdatesAccessor.set(value); }, + /** + * Enables synchronous shadow state updates for ScreenStackHeaderConfig + * on iOS (Fabric, RN 0.82+). On by default. + * PR: https://github.com/software-mansion/react-native-screens/pull/3282 + */ get synchronousHeaderConfigUpdatesEnabled() { return synchronousHeaderConfigUpdatesAccessor.get(); }, From dbb8af02e8088fd4496f701b6762c287951e0201 Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Thu, 23 Apr 2026 17:49:12 +0200 Subject: [PATCH 2/3] remove unnecessary comment --- src/flags.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/flags.ts b/src/flags.ts index 7f284ad248..b6b2b50d8e 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -201,11 +201,6 @@ export const featureFlags = { set synchronousScreenUpdatesEnabled(value: boolean) { synchronousScreenUpdatesAccessor.set(value); }, - /** - * Enables synchronous shadow state updates for ScreenStackHeaderConfig - * on iOS (Fabric, RN 0.82+). On by default. - * PR: https://github.com/software-mansion/react-native-screens/pull/3282 - */ get synchronousHeaderConfigUpdatesEnabled() { return synchronousHeaderConfigUpdatesAccessor.get(); }, From f9fd8c2da5b5c077438d6897ca6eb482b4e5b106 Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Tue, 28 Apr 2026 11:05:44 +0200 Subject: [PATCH 3/3] Move synchronousShadowStateUpdates to initProps --- ios/RNSScreenStackHeaderConfig.mm | 58 ++++++++++++++----------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm index b42d5df2d9..253d7d5fbd 100644 --- a/ios/RNSScreenStackHeaderConfig.mm +++ b/ios/RNSScreenStackHeaderConfig.mm @@ -70,7 +70,6 @@ - (instancetype)initWithFrame:(CGRect)frame _show = YES; _translucent = NO; _addedReactSubviewsInCurrentTransaction = false; - _synchronousShadowStateUpdatesEnabled = YES; _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}); [self initProps]; } @@ -83,6 +82,7 @@ - (void)initProps _reactSubviews = [NSMutableArray new]; _backTitleVisible = YES; _blurEffect = RNSBlurEffectStyleNone; + _synchronousShadowStateUpdatesEnabled = YES; } RNS_IGNORE_SUPER_CALL_BEGIN @@ -282,11 +282,10 @@ - (UIImage *)loadBackButtonImageInViewController:(UIViewController *)vc // in the image attribute not being updated. We manually set frame to the size of an image // in order to trigger proper reload that'd update the image attribute. RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView]; - [imageView reactSetFrame:CGRectMake( - imageView.frame.origin.x, - imageView.frame.origin.y, - imageSource.size.width, - imageSource.size.height)]; + [imageView reactSetFrame:CGRectMake(imageView.frame.origin.x, + imageView.frame.origin.y, + imageSource.size.width, + imageSource.size.height)]; } UIImage *image = imageView.image; @@ -848,13 +847,12 @@ - (void)mountChildComponentView:(UIView *)childCompone return; } - RCTAssert( - childComponentView.superview == nil, - @"Attempt to mount already mounted component view. (parent: %@, child: %@, index: %@, existing parent: %@)", - self, - childComponentView, - @(index), - @([childComponentView.superview tag])); + RCTAssert(childComponentView.superview == nil, + @"Attempt to mount already mounted component view. (parent: %@, child: %@, index: %@, existing parent: %@)", + self, + childComponentView, + @(index), + @([childComponentView.superview tag])); // [_reactSubviews insertObject:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; [self insertReactSubview:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; @@ -1131,23 +1129,21 @@ @implementation RNSScreenStackHeaderConfigManager @implementation RCTConvert (RNSScreenStackHeader) -RCT_ENUM_CONVERTER( - UISemanticContentAttribute, - (@{ - @"ltr" : @(UISemanticContentAttributeForceLeftToRight), - @"rtl" : @(UISemanticContentAttributeForceRightToLeft), - }), - UISemanticContentAttributeUnspecified, - integerValue) - -RCT_ENUM_CONVERTER( - UINavigationItemBackButtonDisplayMode, - (@{ - @"default" : @(UINavigationItemBackButtonDisplayModeDefault), - @"generic" : @(UINavigationItemBackButtonDisplayModeGeneric), - @"minimal" : @(UINavigationItemBackButtonDisplayModeMinimal), - }), - UINavigationItemBackButtonDisplayModeDefault, - integerValue) +RCT_ENUM_CONVERTER(UISemanticContentAttribute, + (@{ + @"ltr" : @(UISemanticContentAttributeForceLeftToRight), + @"rtl" : @(UISemanticContentAttributeForceRightToLeft), + }), + UISemanticContentAttributeUnspecified, + integerValue) + +RCT_ENUM_CONVERTER(UINavigationItemBackButtonDisplayMode, + (@{ + @"default" : @(UINavigationItemBackButtonDisplayModeDefault), + @"generic" : @(UINavigationItemBackButtonDisplayModeGeneric), + @"minimal" : @(UINavigationItemBackButtonDisplayModeMinimal), + }), + UINavigationItemBackButtonDisplayModeDefault, + integerValue) @end