@react-navigation/stack 8.0.0-alpha.24 → 8.0.0-alpha.26

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.
@@ -258,6 +258,27 @@ export function getAnimationEnabled(animation: StackAnimationName | undefined) {
258
258
  return getDefaultAnimation(animation) !== 'none';
259
259
  }
260
260
 
261
+ const getAllRoutes = (
262
+ routes: Route<string>[],
263
+ state: StackNavigationState<ParamListBase>
264
+ ) => {
265
+ const routeKeys = new Set(routes.map((route) => route.key));
266
+ const inactiveRoutes = state.routes.slice(state.index + 1);
267
+
268
+ // If a route is moved from active routes to inactive routes,
269
+ // It can still be in the local copy of `routes` until the animation ends
270
+ // So we need to deduplicate the routes to avoid rendering the same route twice
271
+ return [
272
+ ...routes,
273
+ ...inactiveRoutes.filter((route) => !routeKeys.has(route.key)),
274
+ ];
275
+ };
276
+
277
+ const isInactiveRoute = (route: Route<string>, routes: Route<string>[]) =>
278
+ // `routes` contains active routes and routes animating during transitions.
279
+ // Any route added by `getAllRoutes` that's missing from this list is inactive.
280
+ !routes.some((currentRoute) => currentRoute.key === route.key);
281
+
261
282
  export class CardStack extends React.Component<Props, State> {
262
283
  static getDerivedStateFromProps(
263
284
  props: Props,
@@ -270,10 +291,9 @@ export class CardStack extends React.Component<Props, State> {
270
291
  return null;
271
292
  }
272
293
 
273
- const gestures = [
274
- ...props.routes,
275
- ...props.state.preloadedRoutes,
276
- ].reduce<GestureValues>((acc, curr) => {
294
+ const allRoutes = getAllRoutes(props.routes, props.state);
295
+
296
+ const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
277
297
  const descriptor = props.descriptors[curr.key];
278
298
  const { animation } = descriptor?.options || {};
279
299
 
@@ -282,7 +302,7 @@ export class CardStack extends React.Component<Props, State> {
282
302
  new Animated.Value(
283
303
  (props.openingRouteKeys.includes(curr.key) &&
284
304
  getAnimationEnabled(animation)) ||
285
- props.state.preloadedRoutes.includes(curr)
305
+ isInactiveRoute(curr, props.routes)
286
306
  ? getDistanceFromOptions(
287
307
  state.layout,
288
308
  descriptor?.options,
@@ -294,171 +314,164 @@ export class CardStack extends React.Component<Props, State> {
294
314
  return acc;
295
315
  }, {});
296
316
 
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
- );
317
+ const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
318
+
319
+ const scenes = allRoutes.map((route, index, self) => {
320
+ // For preloaded or retained screens, we don't care about the previous and the next screen
321
+ const isInactive = isInactiveRoute(route, props.routes);
322
+ const previousRoute = isInactive ? undefined : self[index - 1];
323
+ const nextRoute = isInactive ? undefined : self[index + 1];
324
+
325
+ const oldScene = state.scenes[index];
326
+
327
+ const currentGesture = gestures[route.key];
328
+ const previousGesture = previousRoute
329
+ ? gestures[previousRoute.key]
330
+ : undefined;
331
+ const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
332
+
333
+ const descriptor =
334
+ props.descriptors[route.key] ||
335
+ state.descriptors[route.key] ||
336
+ (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
337
+
338
+ const nextOptions =
339
+ nextRoute &&
340
+ (props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key])
341
+ ?.options;
342
+
343
+ const previousOptions =
344
+ previousRoute &&
345
+ (
346
+ props.descriptors[previousRoute?.key] ||
347
+ state.descriptors[previousRoute?.key]
348
+ )?.options;
349
+
350
+ // When a screen is not the last, it should use next screen's transition config
351
+ // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
352
+ // For example combining a slide and a modal transition would look wrong otherwise
353
+ // With this approach, combining different transition styles in the same navigator mostly looks right
354
+ // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
355
+ // but the majority of the transitions look alright
356
+ const optionsForTransitionConfig =
357
+ index !== self.length - 1 &&
358
+ nextOptions &&
359
+ nextOptions?.presentation !== 'transparentModal'
360
+ ? nextOptions
361
+ : descriptor.options;
362
+
363
+ // Assume modal if there are already modal screens in the stack
364
+ // or current screen is a modal when no presentation is specified
365
+ const isModal = modalRouteKeys.includes(route.key);
366
+
367
+ const animation = getDefaultAnimation(
368
+ optionsForTransitionConfig.animation
369
+ );
356
370
 
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
- },
371
+ const isAnimationEnabled = getAnimationEnabled(animation);
372
+
373
+ const transitionPreset =
374
+ animation !== 'default'
375
+ ? NAMED_TRANSITIONS_PRESETS[animation]
376
+ : optionsForTransitionConfig.presentation === 'transparentModal'
377
+ ? ModalFadeTransition
378
+ : optionsForTransitionConfig.presentation === 'modal' || isModal
379
+ ? ModalTransition
380
+ : DefaultTransition;
381
+
382
+ const {
383
+ gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
384
+ gestureDirection = transitionPreset.gestureDirection,
385
+ transitionSpec = transitionPreset.transitionSpec,
386
+ cardStyleInterpolator = isAnimationEnabled
387
+ ? transitionPreset.cardStyleInterpolator
388
+ : forNoAnimationCard,
389
+ headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
390
+ cardOverlayEnabled = (Platform.OS !== 'ios' &&
391
+ optionsForTransitionConfig.presentation !== 'transparentModal') ||
392
+ getIsModalPresentation(cardStyleInterpolator),
393
+ } = optionsForTransitionConfig;
394
+
395
+ const headerMode: StackHeaderMode =
396
+ descriptor.options.headerMode ??
397
+ (!(
398
+ optionsForTransitionConfig.presentation === 'modal' ||
399
+ optionsForTransitionConfig.presentation === 'transparentModal' ||
400
+ nextOptions?.presentation === 'modal' ||
401
+ nextOptions?.presentation === 'transparentModal' ||
402
+ getIsModalPresentation(cardStyleInterpolator)
403
+ ) &&
404
+ Platform.OS === 'ios' &&
405
+ descriptor.options.header === undefined
406
+ ? 'float'
407
+ : 'screen');
408
+
409
+ const isRTL = props.direction === 'rtl';
410
+
411
+ const scene = {
412
+ route,
413
+ descriptor: {
414
+ ...descriptor,
415
+ options: {
416
+ ...descriptor.options,
417
+ animation,
418
+ cardOverlayEnabled,
419
+ cardStyleInterpolator,
420
+ gestureDirection,
421
+ gestureEnabled,
422
+ headerStyleInterpolator,
423
+ transitionSpec,
424
+ headerMode,
412
425
  },
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
426
+ },
427
+ progress: {
428
+ current: getProgressFromGesture(
429
+ currentGesture,
430
+ state.layout,
431
+ descriptor.options,
432
+ isRTL
433
+ ),
434
+ next:
435
+ nextGesture && nextOptions?.presentation !== 'transparentModal'
430
436
  ? getProgressFromGesture(
431
- previousGesture,
437
+ nextGesture,
432
438
  state.layout,
433
- previousOptions,
439
+ nextOptions,
434
440
  isRTL
435
441
  )
436
442
  : 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;
443
+ previous: previousGesture
444
+ ? getProgressFromGesture(
445
+ previousGesture,
446
+ state.layout,
447
+ previousOptions,
448
+ isRTL
449
+ )
450
+ : undefined,
451
+ },
452
+ __memo: [
453
+ state.layout,
454
+ descriptor,
455
+ nextOptions,
456
+ previousOptions,
457
+ currentGesture,
458
+ nextGesture,
459
+ previousGesture,
460
+ ],
461
+ };
462
+
463
+ if (
464
+ oldScene &&
465
+ scene.__memo.every((it, i) => {
466
+ // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
467
+ return oldScene.__memo[i] === it;
468
+ })
469
+ ) {
470
+ return oldScene;
460
471
  }
461
- );
472
+
473
+ return scene;
474
+ });
462
475
 
463
476
  return {
464
477
  routes: props.routes,
@@ -586,6 +599,8 @@ export class CardStack extends React.Component<Props, State> {
586
599
 
587
600
  const { scenes, layout, gestures, headerHeights } = this.state;
588
601
 
602
+ const allRoutes = getAllRoutes(routes, state);
603
+
589
604
  const focusedRoute = state.routes[state.index];
590
605
 
591
606
  const isFloatHeaderAbsolute = scenes.slice(-2).some((scene) => {
@@ -627,26 +642,15 @@ export class CardStack extends React.Component<Props, State> {
627
642
  ],
628
643
  })}
629
644
  <View style={styles.container} onLayout={this.handleLayout}>
630
- {[...routes, ...state.preloadedRoutes].map((route, index, self) => {
645
+ {allRoutes.map((route, index, self) => {
631
646
  const focused = focusedRoute.key === route.key;
632
647
  const gesture = gestures[route.key];
633
648
  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
- }
649
+ const isInactive = isInactiveRoute(route, this.props.routes);
647
650
 
648
651
  const {
649
652
  inactiveBehavior = 'pause',
653
+ animation,
650
654
  headerShown = true,
651
655
  headerTransparent,
652
656
  } = scene.descriptor.options;
@@ -667,6 +671,16 @@ export class CardStack extends React.Component<Props, State> {
667
671
  isParentModal
668
672
  );
669
673
 
674
+ const isAnimationEnabled =
675
+ getAnimationEnabled(animation) ||
676
+ // Also check next screen's animation,
677
+ // As it will result in both screens being visible
678
+ (scenes[index + 1]
679
+ ? getAnimationEnabled(
680
+ scenes[index + 1].descriptor.options.animation
681
+ )
682
+ : false);
683
+
670
684
  const isNextScreenTransparent =
671
685
  scenes[index + 1]?.descriptor.options.presentation ===
672
686
  'transparentModal';
@@ -683,22 +697,23 @@ export class CardStack extends React.Component<Props, State> {
683
697
 
684
698
  const isBeforeLast = index === routes.length - 2;
685
699
 
686
- // Keep animating and the last two rendered routes visible for smoother transitions.
687
- // Preloaded routes should stay mounted, but remain hidden until focused.
700
+ // Keep animating and the last two rendered routes visible for smoother transitions
688
701
  const isVisible =
689
702
  focused ||
690
- isFocusing ||
691
- isRemoving ||
692
703
  isNextScreenTransparent ||
693
- (!isPreloaded && index >= routes.length - 2);
704
+ // We only need to keep other screens visible when animation is enabled
705
+ (isAnimationEnabled &&
706
+ (isFocusing ||
707
+ isRemoving ||
708
+ // Preloaded and retained screens should stay mounted, but remain hidden until focused
709
+ (!isInactive && index >= routes.length - 2)));
694
710
 
695
711
  const activityMode = // Render focused and animating screens normally
696
712
  focused || isFocusing
697
713
  ? 'normal'
698
714
  : inactiveBehavior === 'none' ||
699
- // Unpause preloaded screens so updates are visible
700
- // This lets preloaded screens initialize
701
- isPreloaded ||
715
+ // Unpause preloaded or retained screens so updates are visible
716
+ isInactive ||
702
717
  // Keep the screen before transparent screen active
703
718
  // This lets the screen under the transparent screen update and animate
704
719
  isNextScreenTransparent ||
@@ -753,7 +768,7 @@ export class CardStack extends React.Component<Props, State> {
753
768
  onTransitionStart={onTransitionStart}
754
769
  onTransitionEnd={onTransitionEnd}
755
770
  isNextScreenTransparent={isNextScreenTransparent}
756
- preloaded={isPreloaded}
771
+ preloaded={isInactive}
757
772
  >
758
773
  <ActivityView
759
774
  mode={activityMode}
@@ -62,10 +62,13 @@ export class StackView extends React.Component<Props, State> {
62
62
  props: Readonly<Props>,
63
63
  state: Readonly<State>
64
64
  ) {
65
- const allRoutes = [...props.state.routes, ...props.state.preloadedRoutes];
66
- const previousRoutes = state.previousState
67
- ? [...state.previousState.routes, ...state.previousState.preloadedRoutes]
68
- : [];
65
+ const allRoutes = props.state.routes;
66
+ const previousRoutes = state.previousState?.routes ?? [];
67
+ const previousFocusedRoute = state.previousState
68
+ ? state.previousState.routes[state.previousState.index]
69
+ : undefined;
70
+ const nextFocusedRouteFromState = props.state.routes[props.state.index];
71
+ const activeRoutes = props.state.routes.slice(0, props.state.index + 1);
69
72
 
70
73
  // If there was no change in routes, we don't need to compute anything
71
74
  if (
@@ -73,6 +76,7 @@ export class StackView extends React.Component<Props, State> {
73
76
  allRoutes.map((r) => r.key),
74
77
  previousRoutes.map((r) => r.key)
75
78
  ) &&
79
+ previousFocusedRoute?.key === nextFocusedRouteFromState?.key &&
76
80
  state.routes.length
77
81
  ) {
78
82
  // If there were any routes being closed or replaced,
@@ -83,16 +87,16 @@ export class StackView extends React.Component<Props, State> {
83
87
  const closingRoutes = state.routes.filter(
84
88
  (r) =>
85
89
  state.closingRouteKeys.includes(r.key) &&
86
- !props.state.routes.some((pr) => pr.key === r.key)
90
+ !activeRoutes.some((pr) => pr.key === r.key)
87
91
  );
88
92
 
89
93
  const replacingRoutes = state.routes.filter(
90
94
  (r) =>
91
95
  state.replacingRouteKeys.includes(r.key) &&
92
- !props.state.routes.some((pr) => pr.key === r.key)
96
+ !activeRoutes.some((pr) => pr.key === r.key)
93
97
  );
94
98
 
95
- let routes: Route<string>[] = props.state.routes.slice();
99
+ let routes: Route<string>[] = activeRoutes.slice();
96
100
 
97
101
  // Replacing routes go before the focused route (they're being covered)
98
102
  if (replacingRoutes.length) {
@@ -117,9 +121,10 @@ export class StackView extends React.Component<Props, State> {
117
121
  routes = routes.map((route) => map[route.key] || route);
118
122
  }
119
123
 
124
+ const routeKeys = new Set(routes.map((route) => route.key));
120
125
  const descriptors = [
121
126
  ...routes,
122
- ...props.state.preloadedRoutes,
127
+ ...props.state.routes.filter((route) => !routeKeys.has(route.key)),
123
128
  ].reduce<StackDescriptorMap>((acc, route) => {
124
129
  acc[route.key] =
125
130
  props.descriptors[route.key] || state.descriptors[route.key];
@@ -137,32 +142,20 @@ export class StackView extends React.Component<Props, State> {
137
142
  // Here we determine which routes were added or removed to animate them
138
143
  // We keep a copy of the route being removed in local state to be able to animate it
139
144
 
140
- let routes =
141
- props.state.index < props.state.routes.length - 1
142
- ? // Remove any extra routes from the state
143
- // The last visible route should be the focused route, i.e. at current index
144
- props.state.routes.slice(0, props.state.index + 1)
145
- : props.state.routes;
146
-
147
145
  let { openingRouteKeys, closingRouteKeys, replacingRouteKeys } = state;
148
146
 
149
147
  // If a route that was closing or being replaced is now back in the routes,
150
148
  // it was added back before the animation finished, so stop tracking it
151
149
  closingRouteKeys = closingRouteKeys.filter(
152
- (key) => !routes.some((r) => r.key === key)
150
+ (key) => !activeRoutes.some((r) => r.key === key)
153
151
  );
154
152
 
155
153
  replacingRouteKeys = replacingRouteKeys.filter(
156
- (key) => !routes.some((r) => r.key === key)
154
+ (key) => !activeRoutes.some((r) => r.key === key)
157
155
  );
158
156
 
159
- // Get previous focused route from previousState (actual focused route, not last in previousRoutes
160
- // 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
- const nextFocusedRoute = routes[routes.length - 1];
157
+ // Get previous focused route from previous active routes
158
+ const nextFocusedRoute = activeRoutes[activeRoutes.length - 1];
166
159
 
167
160
  const isAnimationEnabled = (key: string) => {
168
161
  const descriptor = props.descriptors[key] || state.descriptors[key];
@@ -176,6 +169,8 @@ export class StackView extends React.Component<Props, State> {
176
169
  return descriptor.options.animationTypeForReplace ?? 'push';
177
170
  };
178
171
 
172
+ let routes = activeRoutes;
173
+
179
174
  if (
180
175
  previousFocusedRoute &&
181
176
  previousFocusedRoute.key !== nextFocusedRoute.key
@@ -301,9 +296,10 @@ export class StackView extends React.Component<Props, State> {
301
296
  );
302
297
  }
303
298
 
299
+ const routeKeys = new Set(routes.map((route) => route.key));
304
300
  const descriptors = [
305
301
  ...routes,
306
- ...props.state.preloadedRoutes,
302
+ ...props.state.routes.filter((route) => !routeKeys.has(route.key)),
307
303
  ].reduce<StackDescriptorMap>((acc, route) => {
308
304
  acc[route.key] =
309
305
  props.descriptors[route.key] || state.descriptors[route.key];
@@ -352,25 +348,33 @@ export class StackView extends React.Component<Props, State> {
352
348
  const { state, navigation } = this.props;
353
349
  const { closingRouteKeys, replacingRouteKeys } = this.state;
354
350
 
351
+ const activeRoutes = state.routes.slice(0, state.index + 1);
352
+
355
353
  if (
356
354
  closingRouteKeys.some((key) => key === route.key) &&
357
355
  replacingRouteKeys.every((key) => key !== route.key) &&
358
356
  state.routeNames.includes(route.name) &&
359
- !state.routes.some((r) => r.key === route.key)
357
+ !activeRoutes.some((r) => r.key === route.key)
360
358
  ) {
361
- // If route isn't present in current state, but was closing, assume that a close animation was cancelled
359
+ // If route is no longer active, but was closing, assume that a close animation was cancelled
362
360
  // So we need to add this route back to the state
363
361
  navigation.dispatch((state) => {
364
- const routes = [
365
- ...state.routes.filter((r) => r.key !== route.key),
366
- route,
367
- ];
362
+ const activeRoutes = state.routes
363
+ .slice(0, state.index + 1)
364
+ .filter((r) => r.key !== route.key);
365
+ const inactiveRoutes = state.routes
366
+ .slice(state.index + 1)
367
+ .filter((r) => r.key !== route.key);
368
+
369
+ const routes = [...activeRoutes, route];
368
370
 
369
- return CommonActions.reset({
371
+ const resetState = {
370
372
  ...state,
371
- routes,
372
373
  index: routes.length - 1,
373
- });
374
+ routes: routes.concat(inactiveRoutes),
375
+ };
376
+
377
+ return CommonActions.reset(resetState);
374
378
  });
375
379
  } else {
376
380
  this.setState((state) => {
@@ -408,7 +412,9 @@ export class StackView extends React.Component<Props, State> {
408
412
  private handleCloseRoute = ({ route }: { route: Route<string> }) => {
409
413
  const { state, navigation } = this.props;
410
414
 
411
- if (state.routes.some((r) => r.key === route.key)) {
415
+ const activeRoutes = state.routes.slice(0, state.index + 1);
416
+
417
+ if (activeRoutes.some((r) => r.key === route.key)) {
412
418
  // If a route exists in state, trigger a pop
413
419
  // This will happen in when the route was closed from the card component
414
420
  // e.g. When the close animation triggered from a gesture ends