@react-navigation/stack 8.0.0-alpha.23 → 8.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/stack",
3
3
  "description": "Stack navigator component for iOS and Android with animated transitions and gestures",
4
- "version": "8.0.0-alpha.23",
4
+ "version": "8.0.0-alpha.25",
5
5
  "keywords": [
6
6
  "react-native-component",
7
7
  "react-component",
@@ -45,13 +45,13 @@
45
45
  "clean": "del lib"
46
46
  },
47
47
  "dependencies": {
48
- "@react-navigation/elements": "^3.0.0-alpha.21",
48
+ "@react-navigation/elements": "^3.0.0-alpha.23",
49
49
  "color": "^5.0.3",
50
50
  "use-latest-callback": "^0.3.3"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@jest/globals": "^30.3.0",
54
- "@react-navigation/native": "^8.0.0-alpha.18",
54
+ "@react-navigation/native": "^8.0.0-alpha.20",
55
55
  "@testing-library/react-native": "^13.3.3",
56
56
  "@types/color": "^4.2.0",
57
57
  "@types/react": "~19.2.2",
@@ -66,7 +66,7 @@
66
66
  "typescript": "^6.0.2"
67
67
  },
68
68
  "peerDependencies": {
69
- "@react-navigation/native": "^8.0.0-alpha.18",
69
+ "@react-navigation/native": "^8.0.0-alpha.20",
70
70
  "react": ">= 19.2.0",
71
71
  "react-native": "*",
72
72
  "react-native-gesture-handler": ">= 2.0.0",
@@ -91,5 +91,5 @@
91
91
  ]
92
92
  ]
93
93
  },
94
- "gitHead": "18da535b07fc86a30700efc4d824ef58271d2b8f"
94
+ "gitHead": "d465f4e6b2996e5ed4648b23487e2027e83b263d"
95
95
  }
package/src/index.tsx CHANGED
@@ -9,6 +9,7 @@ import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs';
9
9
  export {
10
10
  createStackNavigator,
11
11
  createStackScreen,
12
+ type StackTypeBag,
12
13
  } from './navigators/createStackNavigator';
13
14
 
14
15
  /**
@@ -1,18 +1,14 @@
1
1
  import {
2
2
  createNavigatorFactory,
3
+ createScreenFactory,
3
4
  type EventArg,
5
+ type NavigatorTypeBagBase,
4
6
  type ParamListBase,
5
7
  type StackActionHelpers,
6
8
  StackActions,
7
9
  type StackNavigationState,
8
10
  StackRouter,
9
11
  type StackRouterOptions,
10
- type StaticConfig,
11
- type StaticParamList,
12
- type StaticScreenConfig,
13
- type StaticScreenConfigLinking,
14
- type StaticScreenConfigScreen,
15
- type TypedNavigator,
16
12
  useLocale,
17
13
  useNavigationBuilder,
18
14
  } from '@react-navigation/native';
@@ -21,7 +17,6 @@ import * as React from 'react';
21
17
  import type {
22
18
  StackNavigationEventMap,
23
19
  StackNavigationOptions,
24
- StackNavigationProp,
25
20
  StackNavigatorProps,
26
21
  } from '../types';
27
22
  import { StackView } from '../views/Stack/StackView';
@@ -96,41 +91,15 @@ function StackNavigator({
96
91
  );
97
92
  }
98
93
 
99
- type StackTypeBag<ParamList extends {}> = {
100
- ParamList: ParamList;
101
- State: StackNavigationState<ParamList>;
94
+ export interface StackTypeBag extends NavigatorTypeBagBase {
95
+ State: StackNavigationState<this['ParamList']>;
102
96
  ScreenOptions: StackNavigationOptions;
103
97
  EventMap: StackNavigationEventMap;
104
- NavigationList: {
105
- [RouteName in keyof ParamList]: StackNavigationProp<ParamList, RouteName>;
106
- };
98
+ ActionHelpers: StackActionHelpers<this['ParamList']>;
107
99
  Navigator: typeof StackNavigator;
108
- };
109
-
110
- export function createStackNavigator<
111
- const ParamList extends ParamListBase,
112
- >(): TypedNavigator<StackTypeBag<ParamList>, undefined>;
113
- export function createStackNavigator<
114
- const Config extends StaticConfig<StackTypeBag<ParamListBase>>,
115
- >(
116
- config: Config
117
- ): TypedNavigator<StackTypeBag<StaticParamList<{ config: Config }>>, Config>;
118
- export function createStackNavigator(config?: unknown) {
119
- return createNavigatorFactory(StackNavigator)(config);
120
100
  }
121
101
 
122
- export function createStackScreen<
123
- const Linking extends StaticScreenConfigLinking,
124
- const Screen extends StaticScreenConfigScreen,
125
- >(
126
- config: StaticScreenConfig<
127
- Linking,
128
- Screen,
129
- StackNavigationState<ParamListBase>,
130
- StackNavigationOptions,
131
- StackNavigationEventMap,
132
- StackNavigationProp<ParamListBase>
133
- >
134
- ) {
135
- return config;
136
- }
102
+ export const createStackNavigator =
103
+ createNavigatorFactory<StackTypeBag>(StackNavigator);
104
+
105
+ export const createStackScreen = createScreenFactory<StackTypeBag>();
package/src/types.tsx CHANGED
@@ -346,12 +346,6 @@ export type StackNavigationOptions = StackHeaderOptions &
346
346
  /**
347
347
  * Style object for the card in stack.
348
348
  * You can provide a custom background color to use instead of the default background here.
349
- *
350
- * You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
351
- * This is useful to implement things like modal dialogs.
352
- *
353
- * You might also need to change the animation of the screen using `cardStyleInterpolator`
354
- * so that the previous screen isn't transformed or invisible.
355
349
  */
356
350
  cardStyle?: StyleProp<ViewStyle>;
357
351
  /**
@@ -194,6 +194,7 @@ function CardContainerInner({
194
194
  gestureVelocityImpact,
195
195
  headerMode,
196
196
  headerShown,
197
+ headerTransparent,
197
198
  transitionSpec,
198
199
  } = scene.descriptor.options;
199
200
 
@@ -284,7 +285,15 @@ function CardContainerInner({
284
285
  getPreviousScene,
285
286
  getFocusedRoute,
286
287
  onContentHeightChange: onHeaderHeightChange,
287
- style: styles.header,
288
+ style: [
289
+ styles.header,
290
+ headerTransparent
291
+ ? {
292
+ // Specify an explicit min height for Android screen readers
293
+ minHeight: headerHeight,
294
+ }
295
+ : null,
296
+ ],
288
297
  })
289
298
  : null}
290
299
  <View style={styles.scene}>
@@ -258,6 +258,39 @@ export function getAnimationEnabled(animation: StackAnimationName | undefined) {
258
258
  return getDefaultAnimation(animation) !== 'none';
259
259
  }
260
260
 
261
+ const getAllRoutes = (
262
+ routes: Route<string>[],
263
+ preloadedRoutes: Route<string>[]
264
+ ) => {
265
+ const routeKeys = new Set(routes.map((route) => route.key));
266
+
267
+ // If a route is moved from `state.routes` to `state.preloadedRoutes`,
268
+ // It can still be in the local copy of `routes` until the animation ends
269
+ // So we need to deduplicate the routes to avoid rendering the same route twice
270
+ return [
271
+ ...routes,
272
+ ...preloadedRoutes.filter((route) => !routeKeys.has(route.key)),
273
+ ];
274
+ };
275
+
276
+ const isPreloadedRoute = (
277
+ route: Route<string>,
278
+ props: {
279
+ routes: Route<string>[];
280
+ state: StackNavigationState<ParamListBase>;
281
+ }
282
+ ) => {
283
+ // The route can be in both `routes` and `preloadedRoutes` until the animation ends
284
+ // Treat it as not preloaded, similar to how removed routes are treated until the animation ends
285
+ if (props.routes.some((currentRoute) => currentRoute.key === route.key)) {
286
+ return false;
287
+ }
288
+
289
+ return props.state.preloadedRoutes.some(
290
+ (currentRoute) => currentRoute.key === route.key
291
+ );
292
+ };
293
+
261
294
  export class CardStack extends React.Component<Props, State> {
262
295
  static getDerivedStateFromProps(
263
296
  props: Props,
@@ -270,10 +303,9 @@ export class CardStack extends React.Component<Props, State> {
270
303
  return null;
271
304
  }
272
305
 
273
- const gestures = [
274
- ...props.routes,
275
- ...props.state.preloadedRoutes,
276
- ].reduce<GestureValues>((acc, curr) => {
306
+ const allRoutes = getAllRoutes(props.routes, props.state.preloadedRoutes);
307
+
308
+ const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
277
309
  const descriptor = props.descriptors[curr.key];
278
310
  const { animation } = descriptor?.options || {};
279
311
 
@@ -282,7 +314,7 @@ export class CardStack extends React.Component<Props, State> {
282
314
  new Animated.Value(
283
315
  (props.openingRouteKeys.includes(curr.key) &&
284
316
  getAnimationEnabled(animation)) ||
285
- props.state.preloadedRoutes.includes(curr)
317
+ isPreloadedRoute(curr, props)
286
318
  ? getDistanceFromOptions(
287
319
  state.layout,
288
320
  descriptor?.options,
@@ -294,171 +326,164 @@ export class CardStack extends React.Component<Props, State> {
294
326
  return acc;
295
327
  }, {});
296
328
 
297
- const modalRouteKeys = getModalRouteKeys(
298
- [...props.routes, ...props.state.preloadedRoutes],
299
- props.descriptors
300
- );
301
-
302
- const scenes = [...props.routes, ...props.state.preloadedRoutes].map(
303
- (route, index, self) => {
304
- // For preloaded screens, we don't care about the previous and the next screen
305
- const isPreloaded = props.state.preloadedRoutes.includes(route);
306
- const previousRoute = isPreloaded ? undefined : self[index - 1];
307
- const nextRoute = isPreloaded ? undefined : self[index + 1];
308
-
309
- const oldScene = state.scenes[index];
310
-
311
- const currentGesture = gestures[route.key];
312
- const previousGesture = previousRoute
313
- ? gestures[previousRoute.key]
314
- : undefined;
315
- const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
316
-
317
- const descriptor =
318
- props.descriptors[route.key] ||
319
- state.descriptors[route.key] ||
320
- (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
321
-
322
- const nextOptions =
323
- nextRoute &&
324
- (
325
- props.descriptors[nextRoute?.key] ||
326
- state.descriptors[nextRoute?.key]
327
- )?.options;
328
-
329
- const previousOptions =
330
- previousRoute &&
331
- (
332
- props.descriptors[previousRoute?.key] ||
333
- state.descriptors[previousRoute?.key]
334
- )?.options;
335
-
336
- // When a screen is not the last, it should use next screen's transition config
337
- // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
338
- // For example combining a slide and a modal transition would look wrong otherwise
339
- // With this approach, combining different transition styles in the same navigator mostly looks right
340
- // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
341
- // but the majority of the transitions look alright
342
- const optionsForTransitionConfig =
343
- index !== self.length - 1 &&
344
- nextOptions &&
345
- nextOptions?.presentation !== 'transparentModal'
346
- ? nextOptions
347
- : descriptor.options;
348
-
349
- // Assume modal if there are already modal screens in the stack
350
- // or current screen is a modal when no presentation is specified
351
- const isModal = modalRouteKeys.includes(route.key);
352
-
353
- const animation = getDefaultAnimation(
354
- optionsForTransitionConfig.animation
355
- );
329
+ const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
330
+
331
+ const scenes = allRoutes.map((route, index, self) => {
332
+ // For preloaded screens, we don't care about the previous and the next screen
333
+ const isPreloaded = isPreloadedRoute(route, props);
334
+ const previousRoute = isPreloaded ? undefined : self[index - 1];
335
+ const nextRoute = isPreloaded ? undefined : self[index + 1];
336
+
337
+ const oldScene = state.scenes[index];
338
+
339
+ const currentGesture = gestures[route.key];
340
+ const previousGesture = previousRoute
341
+ ? gestures[previousRoute.key]
342
+ : undefined;
343
+ const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
344
+
345
+ const descriptor =
346
+ props.descriptors[route.key] ||
347
+ state.descriptors[route.key] ||
348
+ (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
349
+
350
+ const nextOptions =
351
+ nextRoute &&
352
+ (props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key])
353
+ ?.options;
354
+
355
+ const previousOptions =
356
+ previousRoute &&
357
+ (
358
+ props.descriptors[previousRoute?.key] ||
359
+ state.descriptors[previousRoute?.key]
360
+ )?.options;
361
+
362
+ // When a screen is not the last, it should use next screen's transition config
363
+ // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
364
+ // For example combining a slide and a modal transition would look wrong otherwise
365
+ // With this approach, combining different transition styles in the same navigator mostly looks right
366
+ // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
367
+ // but the majority of the transitions look alright
368
+ const optionsForTransitionConfig =
369
+ index !== self.length - 1 &&
370
+ nextOptions &&
371
+ nextOptions?.presentation !== 'transparentModal'
372
+ ? nextOptions
373
+ : descriptor.options;
374
+
375
+ // Assume modal if there are already modal screens in the stack
376
+ // or current screen is a modal when no presentation is specified
377
+ const isModal = modalRouteKeys.includes(route.key);
378
+
379
+ const animation = getDefaultAnimation(
380
+ optionsForTransitionConfig.animation
381
+ );
356
382
 
357
- const isAnimationEnabled = getAnimationEnabled(animation);
358
-
359
- const transitionPreset =
360
- animation !== 'default'
361
- ? NAMED_TRANSITIONS_PRESETS[animation]
362
- : optionsForTransitionConfig.presentation === 'transparentModal'
363
- ? ModalFadeTransition
364
- : optionsForTransitionConfig.presentation === 'modal' || isModal
365
- ? ModalTransition
366
- : DefaultTransition;
367
-
368
- const {
369
- gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
370
- gestureDirection = transitionPreset.gestureDirection,
371
- transitionSpec = transitionPreset.transitionSpec,
372
- cardStyleInterpolator = isAnimationEnabled
373
- ? transitionPreset.cardStyleInterpolator
374
- : forNoAnimationCard,
375
- headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
376
- cardOverlayEnabled = (Platform.OS !== 'ios' &&
377
- optionsForTransitionConfig.presentation !== 'transparentModal') ||
378
- getIsModalPresentation(cardStyleInterpolator),
379
- } = optionsForTransitionConfig;
380
-
381
- const headerMode: StackHeaderMode =
382
- descriptor.options.headerMode ??
383
- (!(
384
- optionsForTransitionConfig.presentation === 'modal' ||
385
- optionsForTransitionConfig.presentation === 'transparentModal' ||
386
- nextOptions?.presentation === 'modal' ||
387
- nextOptions?.presentation === 'transparentModal' ||
388
- getIsModalPresentation(cardStyleInterpolator)
389
- ) &&
390
- Platform.OS === 'ios' &&
391
- descriptor.options.header === undefined
392
- ? 'float'
393
- : 'screen');
394
-
395
- const isRTL = props.direction === 'rtl';
396
-
397
- const scene = {
398
- route,
399
- descriptor: {
400
- ...descriptor,
401
- options: {
402
- ...descriptor.options,
403
- animation,
404
- cardOverlayEnabled,
405
- cardStyleInterpolator,
406
- gestureDirection,
407
- gestureEnabled,
408
- headerStyleInterpolator,
409
- transitionSpec,
410
- headerMode,
411
- },
383
+ const isAnimationEnabled = getAnimationEnabled(animation);
384
+
385
+ const transitionPreset =
386
+ animation !== 'default'
387
+ ? NAMED_TRANSITIONS_PRESETS[animation]
388
+ : optionsForTransitionConfig.presentation === 'transparentModal'
389
+ ? ModalFadeTransition
390
+ : optionsForTransitionConfig.presentation === 'modal' || isModal
391
+ ? ModalTransition
392
+ : DefaultTransition;
393
+
394
+ const {
395
+ gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
396
+ gestureDirection = transitionPreset.gestureDirection,
397
+ transitionSpec = transitionPreset.transitionSpec,
398
+ cardStyleInterpolator = isAnimationEnabled
399
+ ? transitionPreset.cardStyleInterpolator
400
+ : forNoAnimationCard,
401
+ headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
402
+ cardOverlayEnabled = (Platform.OS !== 'ios' &&
403
+ optionsForTransitionConfig.presentation !== 'transparentModal') ||
404
+ getIsModalPresentation(cardStyleInterpolator),
405
+ } = optionsForTransitionConfig;
406
+
407
+ const headerMode: StackHeaderMode =
408
+ descriptor.options.headerMode ??
409
+ (!(
410
+ optionsForTransitionConfig.presentation === 'modal' ||
411
+ optionsForTransitionConfig.presentation === 'transparentModal' ||
412
+ nextOptions?.presentation === 'modal' ||
413
+ nextOptions?.presentation === 'transparentModal' ||
414
+ getIsModalPresentation(cardStyleInterpolator)
415
+ ) &&
416
+ Platform.OS === 'ios' &&
417
+ descriptor.options.header === undefined
418
+ ? 'float'
419
+ : 'screen');
420
+
421
+ const isRTL = props.direction === 'rtl';
422
+
423
+ const scene = {
424
+ route,
425
+ descriptor: {
426
+ ...descriptor,
427
+ options: {
428
+ ...descriptor.options,
429
+ animation,
430
+ cardOverlayEnabled,
431
+ cardStyleInterpolator,
432
+ gestureDirection,
433
+ gestureEnabled,
434
+ headerStyleInterpolator,
435
+ transitionSpec,
436
+ headerMode,
412
437
  },
413
- progress: {
414
- current: getProgressFromGesture(
415
- currentGesture,
416
- state.layout,
417
- descriptor.options,
418
- isRTL
419
- ),
420
- next:
421
- nextGesture && nextOptions?.presentation !== 'transparentModal'
422
- ? getProgressFromGesture(
423
- nextGesture,
424
- state.layout,
425
- nextOptions,
426
- isRTL
427
- )
428
- : undefined,
429
- previous: previousGesture
438
+ },
439
+ progress: {
440
+ current: getProgressFromGesture(
441
+ currentGesture,
442
+ state.layout,
443
+ descriptor.options,
444
+ isRTL
445
+ ),
446
+ next:
447
+ nextGesture && nextOptions?.presentation !== 'transparentModal'
430
448
  ? getProgressFromGesture(
431
- previousGesture,
449
+ nextGesture,
432
450
  state.layout,
433
- previousOptions,
451
+ nextOptions,
434
452
  isRTL
435
453
  )
436
454
  : undefined,
437
- },
438
- __memo: [
439
- state.layout,
440
- descriptor,
441
- nextOptions,
442
- previousOptions,
443
- currentGesture,
444
- nextGesture,
445
- previousGesture,
446
- ],
447
- };
448
-
449
- if (
450
- oldScene &&
451
- scene.__memo.every((it, i) => {
452
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
453
- return oldScene.__memo[i] === it;
454
- })
455
- ) {
456
- return oldScene;
457
- }
458
-
459
- return scene;
455
+ previous: previousGesture
456
+ ? getProgressFromGesture(
457
+ previousGesture,
458
+ state.layout,
459
+ previousOptions,
460
+ isRTL
461
+ )
462
+ : undefined,
463
+ },
464
+ __memo: [
465
+ state.layout,
466
+ descriptor,
467
+ nextOptions,
468
+ previousOptions,
469
+ currentGesture,
470
+ nextGesture,
471
+ previousGesture,
472
+ ],
473
+ };
474
+
475
+ if (
476
+ oldScene &&
477
+ scene.__memo.every((it, i) => {
478
+ // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
479
+ return oldScene.__memo[i] === it;
480
+ })
481
+ ) {
482
+ return oldScene;
460
483
  }
461
- );
484
+
485
+ return scene;
486
+ });
462
487
 
463
488
  return {
464
489
  routes: props.routes,
@@ -586,6 +611,8 @@ export class CardStack extends React.Component<Props, State> {
586
611
 
587
612
  const { scenes, layout, gestures, headerHeights } = this.state;
588
613
 
614
+ const allRoutes = getAllRoutes(routes, state.preloadedRoutes);
615
+
589
616
  const focusedRoute = state.routes[state.index];
590
617
 
591
618
  const isFloatHeaderAbsolute = scenes.slice(-2).some((scene) => {
@@ -627,26 +654,15 @@ export class CardStack extends React.Component<Props, State> {
627
654
  ],
628
655
  })}
629
656
  <View style={styles.container} onLayout={this.handleLayout}>
630
- {[...routes, ...state.preloadedRoutes].map((route, index, self) => {
657
+ {allRoutes.map((route, index, self) => {
631
658
  const focused = focusedRoute.key === route.key;
632
659
  const gesture = gestures[route.key];
633
660
  const scene = scenes[index];
634
- // It is possible that for a short period the route appears in both arrays.
635
- // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
636
- // However, due to the router action, it immediately populates the `preloadedRoutes` array.
637
- // Practically, the logic below takes care that it is rendered only once.
638
- const isPreloaded =
639
- state.preloadedRoutes.includes(route) && !routes.includes(route);
640
- if (
641
- state.preloadedRoutes.includes(route) &&
642
- routes.includes(route) &&
643
- index >= routes.length
644
- ) {
645
- return null;
646
- }
661
+ const isPreloaded = isPreloadedRoute(route, this.props);
647
662
 
648
663
  const {
649
664
  inactiveBehavior = 'pause',
665
+ animation,
650
666
  headerShown = true,
651
667
  headerTransparent,
652
668
  } = scene.descriptor.options;
@@ -667,6 +683,16 @@ export class CardStack extends React.Component<Props, State> {
667
683
  isParentModal
668
684
  );
669
685
 
686
+ const isAnimationEnabled =
687
+ getAnimationEnabled(animation) ||
688
+ // Also check next screen's animation,
689
+ // As it will result in both screens being visible
690
+ (scenes[index + 1]
691
+ ? getAnimationEnabled(
692
+ scenes[index + 1].descriptor.options.animation
693
+ )
694
+ : false);
695
+
670
696
  const isNextScreenTransparent =
671
697
  scenes[index + 1]?.descriptor.options.presentation ===
672
698
  'transparentModal';
@@ -683,14 +709,16 @@ export class CardStack extends React.Component<Props, State> {
683
709
 
684
710
  const isBeforeLast = index === routes.length - 2;
685
711
 
686
- // Keep animating and the last two rendered routes visible for smoother transitions.
687
- // Preloaded routes should stay mounted, but remain hidden until focused.
712
+ // Keep animating and the last two rendered routes visible for smoother transitions
688
713
  const isVisible =
689
714
  focused ||
690
- isFocusing ||
691
- isRemoving ||
692
715
  isNextScreenTransparent ||
693
- (!isPreloaded && index >= routes.length - 2);
716
+ // We only need to keep other screens visible when animation is enabled
717
+ (isAnimationEnabled &&
718
+ (isFocusing ||
719
+ isRemoving ||
720
+ // Preloaded routes should stay mounted, but remain hidden until focused
721
+ (!isPreloaded && index >= routes.length - 2)));
694
722
 
695
723
  const activityMode = // Render focused and animating screens normally
696
724
  focused || isFocusing
@@ -66,6 +66,10 @@ export class StackView extends React.Component<Props, State> {
66
66
  const previousRoutes = state.previousState
67
67
  ? [...state.previousState.routes, ...state.previousState.preloadedRoutes]
68
68
  : [];
69
+ const previousFocusedRoute = state.previousState
70
+ ? state.previousState.routes[state.previousState.index]
71
+ : undefined;
72
+ const nextFocusedRouteFromState = props.state.routes[props.state.index];
69
73
 
70
74
  // If there was no change in routes, we don't need to compute anything
71
75
  if (
@@ -73,6 +77,7 @@ export class StackView extends React.Component<Props, State> {
73
77
  allRoutes.map((r) => r.key),
74
78
  previousRoutes.map((r) => r.key)
75
79
  ) &&
80
+ previousFocusedRoute?.key === nextFocusedRouteFromState?.key &&
76
81
  state.routes.length
77
82
  ) {
78
83
  // If there were any routes being closed or replaced,
@@ -158,10 +163,6 @@ export class StackView extends React.Component<Props, State> {
158
163
 
159
164
  // Get previous focused route from previousState (actual focused route, not last in previousRoutes
160
165
  // which can be a preloaded route that was never focused)
161
- const previousFocusedRoute = state.previousState
162
- ? state.previousState.routes[state.previousState.index]
163
- : undefined;
164
-
165
166
  const nextFocusedRoute = routes[routes.length - 1];
166
167
 
167
168
  const isAnimationEnabled = (key: string) => {