@react-navigation/routers 8.0.0-alpha.0 → 8.0.0-alpha.10

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.
@@ -6,7 +6,6 @@ import { createRouteFromAction } from './createRouteFromAction';
6
6
  import type {
7
7
  CommonNavigationAction,
8
8
  DefaultRouterOptions,
9
- NavigationRoute,
10
9
  NavigationState,
11
10
  ParamListBase,
12
11
  Route,
@@ -16,36 +15,42 @@ import type {
16
15
  export type StackActionType =
17
16
  | {
18
17
  type: 'REPLACE';
19
- payload: { name: string; params?: object };
20
- source?: string;
21
- target?: string;
18
+ payload: { name: string; params?: object | undefined };
19
+ source?: string | undefined;
20
+ target?: string | undefined;
22
21
  }
23
22
  | {
24
23
  type: 'PUSH';
25
- payload: { name: string; params?: object };
26
- source?: string;
27
- target?: string;
24
+ payload: { name: string; params?: object | undefined };
25
+ source?: string | undefined;
26
+ target?: string | undefined;
28
27
  }
29
28
  | {
30
29
  type: 'POP';
31
30
  payload: { count: number };
32
- source?: string;
33
- target?: string;
31
+ source?: string | undefined;
32
+ target?: string | undefined;
34
33
  }
35
34
  | {
36
35
  type: 'POP_TO_TOP';
37
- source?: string;
38
- target?: string;
36
+ source?: string | undefined;
37
+ target?: string | undefined;
39
38
  }
40
39
  | {
41
40
  type: 'POP_TO';
42
41
  payload: {
43
42
  name: string;
44
- params?: object;
45
- merge?: boolean;
43
+ params?: object | undefined;
44
+ merge?: boolean | undefined;
46
45
  };
47
- source?: string;
48
- target?: string;
46
+ source?: string | undefined;
47
+ target?: string | undefined;
48
+ }
49
+ | {
50
+ type: 'RETAIN';
51
+ payload: { enable: boolean };
52
+ source?: string | undefined;
53
+ target?: string | undefined;
49
54
  };
50
55
 
51
56
  export type StackRouterOptions = DefaultRouterOptions;
@@ -57,9 +62,11 @@ export type StackNavigationState<ParamList extends ParamListBase> =
57
62
  */
58
63
  type: 'stack';
59
64
  /**
60
- * List of routes, which are supposed to be preloaded before navigating to.
65
+ * List of route keys to retain in the stack.
66
+ * When a retained route gets removed from the stack,
67
+ * it'll be kept in routes and can be navigated to again.
61
68
  */
62
- preloadedRoutes: NavigationRoute<ParamList, keyof ParamList>[];
69
+ retainedRouteKeys: string[];
63
70
  };
64
71
 
65
72
  export type StackActionHelpers<ParamList extends ParamListBase> = {
@@ -124,6 +131,15 @@ export type StackActionHelpers<ParamList extends ParamListBase> = {
124
131
  ]
125
132
  : never
126
133
  ): void;
134
+
135
+ /**
136
+ * Enable or disable retaining the current route in the stack.
137
+ * When a retained route gets removed from the stack,
138
+ * it'll be kept in routes and can be navigated to again.
139
+ *
140
+ * @param enable Whether to retain the current route in the stack or not.
141
+ */
142
+ retain(enable: boolean): void;
127
143
  };
128
144
 
129
145
  export const StackActions = {
@@ -158,6 +174,103 @@ export const StackActions = {
158
174
  },
159
175
  } as const satisfies StackActionType;
160
176
  },
177
+ retain(enable: boolean) {
178
+ return {
179
+ type: 'RETAIN',
180
+ payload: { enable },
181
+ } as const satisfies StackActionType;
182
+ },
183
+ };
184
+
185
+ function retainRoutes<ParamList extends ParamListBase>(
186
+ previousState: StackNavigationState<ParamList>,
187
+ nextState: StackNavigationState<ParamList>
188
+ ) {
189
+ if (!nextState.retainedRouteKeys.length) {
190
+ return nextState;
191
+ }
192
+
193
+ const retainedRouteKeySet = new Set(nextState.retainedRouteKeys);
194
+ const nextRouteKeySet = new Set(nextState.routes.map((route) => route.key));
195
+ const retainedRoutes = getActiveRoutes(previousState).filter(
196
+ (route) =>
197
+ retainedRouteKeySet.has(route.key) && !nextRouteKeySet.has(route.key)
198
+ );
199
+
200
+ if (!retainedRoutes.length) {
201
+ return nextState;
202
+ }
203
+
204
+ const removedRetainedRouteKeys = new Set(
205
+ retainedRoutes.map((route) => route.key)
206
+ );
207
+
208
+ return getStateWithRoutes(
209
+ nextState,
210
+ getActiveRoutes(nextState),
211
+ getPreloadedRoutes(nextState),
212
+ retainedRoutes.concat(
213
+ getRetainedRoutes(nextState).filter(
214
+ (route) => !removedRetainedRouteKeys.has(route.key)
215
+ )
216
+ )
217
+ );
218
+ }
219
+
220
+ const getActiveRoutes = <ParamList extends ParamListBase>(
221
+ state: StackNavigationState<ParamList>
222
+ ) => state.routes.slice(0, state.index + 1);
223
+
224
+ const getInactiveRoutes = <ParamList extends ParamListBase>(
225
+ state: StackNavigationState<ParamList>
226
+ ) => state.routes.slice(state.index + 1);
227
+
228
+ const getPreloadedRoutes = <ParamList extends ParamListBase>(
229
+ state: StackNavigationState<ParamList>
230
+ ) => {
231
+ const retainedRouteKeySet = new Set(state.retainedRouteKeys);
232
+
233
+ return getInactiveRoutes(state).filter(
234
+ (route) => !retainedRouteKeySet.has(route.key)
235
+ );
236
+ };
237
+
238
+ const getRetainedRoutes = <ParamList extends ParamListBase>(
239
+ state: StackNavigationState<ParamList>
240
+ ) => {
241
+ const retainedRouteKeySet = new Set(state.retainedRouteKeys);
242
+
243
+ return getInactiveRoutes(state).filter((route) =>
244
+ retainedRouteKeySet.has(route.key)
245
+ );
246
+ };
247
+
248
+ const getStateWithRoutes = <ParamList extends ParamListBase>(
249
+ state: StackNavigationState<ParamList>,
250
+ activeRoutes: Route<string>[],
251
+ preloadedRoutes = getPreloadedRoutes(state),
252
+ retainedRoutes = getRetainedRoutes(state)
253
+ ) => {
254
+ const routeKeySet = new Set(activeRoutes.map((route) => route.key));
255
+ const retainedRouteKeySet = new Set(state.retainedRouteKeys);
256
+
257
+ const filteredRetainedRoutes = retainedRoutes.filter(
258
+ (route) => !routeKeySet.has(route.key)
259
+ );
260
+
261
+ const filteredPreloadedRoutes = preloadedRoutes.filter(
262
+ (route) =>
263
+ !retainedRouteKeySet.has(route.key) && !routeKeySet.has(route.key)
264
+ );
265
+
266
+ return {
267
+ ...state,
268
+ index: activeRoutes.length - 1,
269
+ routes: activeRoutes.concat(
270
+ filteredRetainedRoutes,
271
+ filteredPreloadedRoutes
272
+ ),
273
+ };
161
274
  };
162
275
 
163
276
  export function StackRouter(options: StackRouterOptions) {
@@ -182,7 +295,7 @@ export function StackRouter(options: StackRouterOptions) {
182
295
  key: `stack-${nanoid()}`,
183
296
  index: 0,
184
297
  routeNames,
185
- preloadedRoutes: [],
298
+ retainedRouteKeys: [],
186
299
  routes: [
187
300
  {
188
301
  key: `${initialRouteName}-${nanoid()}`,
@@ -214,24 +327,6 @@ export function StackRouter(options: StackRouterOptions) {
214
327
  : route.params,
215
328
  }));
216
329
 
217
- const preloadedRoutes =
218
- state.preloadedRoutes
219
- ?.filter((route) => routeNames.includes(route.name))
220
- .map(
221
- (route) =>
222
- ({
223
- ...route,
224
- key: route.key || `${route.name}-${nanoid()}`,
225
- params:
226
- routeParamList[route.name] !== undefined
227
- ? {
228
- ...routeParamList[route.name],
229
- ...route.params,
230
- }
231
- : route.params,
232
- }) as Route<string>
233
- ) ?? [];
234
-
235
330
  if (routes.length === 0) {
236
331
  const initialRouteName =
237
332
  options.initialRouteName !== undefined
@@ -245,14 +340,23 @@ export function StackRouter(options: StackRouterOptions) {
245
340
  });
246
341
  }
247
342
 
343
+ const index = Math.min(
344
+ Math.max(state.index ?? routes.length - 1, 0),
345
+ routes.length - 1
346
+ );
347
+
348
+ const routeKeys = new Set(routes.map((route) => route.key));
349
+ const retainedRouteKeys =
350
+ state.retainedRouteKeys?.filter((key) => routeKeys.has(key)) ?? [];
351
+
248
352
  return {
249
353
  stale: false,
250
354
  type: 'stack',
251
355
  key: `stack-${nanoid()}`,
252
- index: routes.length - 1,
356
+ index,
253
357
  routeNames,
254
358
  routes,
255
- preloadedRoutes,
359
+ retainedRouteKeys,
256
360
  };
257
361
  },
258
362
 
@@ -260,7 +364,19 @@ export function StackRouter(options: StackRouterOptions) {
260
364
  state,
261
365
  { routeNames, routeParamList, routeKeyChanges }
262
366
  ) {
263
- const routes = state.routes.filter(
367
+ const routes = getActiveRoutes(state).filter(
368
+ (route) =>
369
+ routeNames.includes(route.name) &&
370
+ !routeKeyChanges.includes(route.name)
371
+ );
372
+
373
+ const preloadedRoutes = getPreloadedRoutes(state).filter(
374
+ (route) =>
375
+ routeNames.includes(route.name) &&
376
+ !routeKeyChanges.includes(route.name)
377
+ );
378
+
379
+ const retainedRoutes = getRetainedRoutes(state).filter(
264
380
  (route) =>
265
381
  routeNames.includes(route.name) &&
266
382
  !routeKeyChanges.includes(route.name)
@@ -280,36 +396,54 @@ export function StackRouter(options: StackRouterOptions) {
280
396
  });
281
397
  }
282
398
 
283
- return {
284
- ...state,
285
- routeNames,
399
+ const routeKeys = new Set(
400
+ [...routes, ...preloadedRoutes, ...retainedRoutes].map(
401
+ (route) => route.key
402
+ )
403
+ );
404
+
405
+ const retainedRouteKeys = state.retainedRouteKeys.filter((key) =>
406
+ routeKeys.has(key)
407
+ );
408
+
409
+ return getStateWithRoutes(
410
+ {
411
+ ...state,
412
+ routeNames,
413
+ retainedRouteKeys,
414
+ },
286
415
  routes,
287
- index: Math.min(state.index, routes.length - 1),
288
- };
416
+ preloadedRoutes,
417
+ retainedRoutes
418
+ );
289
419
  },
290
420
 
291
421
  getStateForRouteFocus(state, key) {
292
- const index = state.routes.findIndex((r) => r.key === key);
422
+ const routes = getActiveRoutes(state);
423
+ const index = routes.findIndex((r) => r.key === key);
293
424
 
294
425
  if (index === -1 || index === state.index) {
295
426
  return state;
296
427
  }
297
428
 
298
- return {
299
- ...state,
300
- index,
301
- routes: state.routes.slice(0, index + 1),
302
- };
429
+ return retainRoutes(
430
+ state,
431
+ getStateWithRoutes(state, routes.slice(0, index + 1))
432
+ );
303
433
  },
304
434
 
305
435
  getStateForAction(state, action, options) {
306
436
  const { routeParamList } = options;
307
437
 
438
+ const routes = getActiveRoutes(state);
439
+ const preloadedRoutes = getPreloadedRoutes(state);
440
+ const inactiveRoutes = getInactiveRoutes(state);
441
+
308
442
  switch (action.type) {
309
443
  case 'REPLACE': {
310
444
  const currentIndex =
311
445
  action.target === state.key && action.source
312
- ? state.routes.findIndex((r) => r.key === action.source)
446
+ ? routes.findIndex((r) => r.key === action.source)
313
447
  : state.index;
314
448
 
315
449
  if (currentIndex === -1) {
@@ -323,8 +457,8 @@ export function StackRouter(options: StackRouterOptions) {
323
457
  const getId = options.routeGetIdList[action.payload.name];
324
458
  const id = getId?.({ params: action.payload.params });
325
459
 
326
- // Re-use preloaded route if available
327
- let route = state.preloadedRoutes.find(
460
+ // Re-use inactive route if available
461
+ let route = inactiveRoutes.find(
328
462
  (route) =>
329
463
  route.name === action.payload.name &&
330
464
  id === getId?.({ params: route.params })
@@ -332,17 +466,22 @@ export function StackRouter(options: StackRouterOptions) {
332
466
 
333
467
  if (!route) {
334
468
  route = createRouteFromAction({ action, routeParamList });
469
+ } else {
470
+ const params = createParamsFromAction({ action, routeParamList });
471
+
472
+ if (route.params !== params) {
473
+ route = { ...route, params };
474
+ }
335
475
  }
336
476
 
337
- return {
338
- ...state,
339
- routes: state.routes.map((r, i) =>
340
- i === currentIndex ? route : r
341
- ),
342
- preloadedRoutes: state.preloadedRoutes.filter(
343
- (r) => r.key !== route.key
344
- ),
345
- };
477
+ return retainRoutes(
478
+ state,
479
+ getStateWithRoutes(
480
+ state,
481
+ routes.map((r, i) => (i === currentIndex ? route : r)),
482
+ preloadedRoutes.filter((r) => r.key !== route.key)
483
+ )
484
+ );
346
485
  }
347
486
 
348
487
  case 'PUSH':
@@ -356,27 +495,63 @@ export function StackRouter(options: StackRouterOptions) {
356
495
 
357
496
  let route: Route<string> | undefined;
358
497
 
359
- if (id !== undefined) {
360
- route = state.routes.findLast(
361
- (route) =>
362
- route.name === action.payload.name &&
363
- id === getId?.({ params: route.params })
364
- );
365
- } else if (action.type === 'NAVIGATE') {
366
- const currentRoute = state.routes[state.index];
367
-
368
- // If the route matches the current one, then navigate to it
369
- if (action.payload.name === currentRoute.name) {
370
- route = currentRoute;
371
- } else if (action.payload.pop) {
372
- route = state.routes.findLast(
373
- (route) => route.name === action.payload.name
374
- );
498
+ if (action.type === 'NAVIGATE') {
499
+ const currentRoute = routes[state.index];
500
+
501
+ if (id !== undefined) {
502
+ if (
503
+ currentRoute.name === action.payload.name &&
504
+ id === getId?.({ params: currentRoute.params })
505
+ ) {
506
+ route = currentRoute;
507
+ } else if (action.payload.pop) {
508
+ for (let i = routes.length - 1; i >= 0; i--) {
509
+ const r = routes[i];
510
+
511
+ if (r.name !== action.payload.name) {
512
+ continue;
513
+ }
514
+
515
+ if (id === getId?.({ params: r.params })) {
516
+ route = r;
517
+ break;
518
+ }
519
+
520
+ if (r.history?.length) {
521
+ for (let j = r.history.length - 1; j >= 0; j--) {
522
+ if (
523
+ r.history[j].type === 'params' &&
524
+ id === getId?.({ params: r.history[j].params })
525
+ ) {
526
+ route = {
527
+ ...r,
528
+ params: r.history[j].params,
529
+ history: r.history.slice(0, j),
530
+ };
531
+ break;
532
+ }
533
+ }
534
+
535
+ if (route) {
536
+ break;
537
+ }
538
+ }
539
+ }
540
+ }
541
+ } else {
542
+ // If the route matches the current one, then navigate to it
543
+ if (action.payload.name === currentRoute.name) {
544
+ route = currentRoute;
545
+ } else if (action.payload.pop) {
546
+ route = routes.findLast(
547
+ (route) => route.name === action.payload.name
548
+ );
549
+ }
375
550
  }
376
551
  }
377
552
 
378
553
  if (!route) {
379
- route = state.preloadedRoutes.find(
554
+ route = inactiveRoutes.find(
380
555
  (route) =>
381
556
  route.name === action.payload.name &&
382
557
  id === getId?.({ params: route.params })
@@ -399,16 +574,16 @@ export function StackRouter(options: StackRouterOptions) {
399
574
  params = createParamsFromAction({ action, routeParamList });
400
575
  }
401
576
 
402
- let routes: Route<string>[];
577
+ let nextRoutes: Route<string>[];
403
578
 
404
579
  if (route) {
405
580
  if (action.type === 'NAVIGATE' && action.payload.pop) {
406
- routes = [];
581
+ nextRoutes = [];
407
582
 
408
583
  // Get all routes until the matching one
409
- for (const r of state.routes) {
584
+ for (const r of routes) {
410
585
  if (r.key === route.key) {
411
- routes.push({
586
+ nextRoutes.push({
412
587
  ...route,
413
588
  path:
414
589
  action.payload.path !== undefined
@@ -419,11 +594,22 @@ export function StackRouter(options: StackRouterOptions) {
419
594
  break;
420
595
  }
421
596
 
422
- routes.push(r);
597
+ nextRoutes.push(r);
598
+ }
599
+
600
+ if (!nextRoutes.some((r) => r.key === route.key)) {
601
+ nextRoutes.push({
602
+ ...route,
603
+ path:
604
+ action.payload.path !== undefined
605
+ ? action.payload.path
606
+ : route.path,
607
+ params,
608
+ });
423
609
  }
424
610
  } else {
425
- routes = state.routes.filter((r) => r.key !== route.key);
426
- routes.push({
611
+ nextRoutes = routes.filter((r) => r.key !== route.key);
612
+ nextRoutes.push({
427
613
  ...route,
428
614
  path:
429
615
  action.type === 'NAVIGATE' &&
@@ -434,8 +620,8 @@ export function StackRouter(options: StackRouterOptions) {
434
620
  });
435
621
  }
436
622
  } else {
437
- routes = [
438
- ...state.routes,
623
+ nextRoutes = [
624
+ ...routes,
439
625
  {
440
626
  key: `${action.payload.name}-${nanoid()}`,
441
627
  name: action.payload.name,
@@ -446,23 +632,29 @@ export function StackRouter(options: StackRouterOptions) {
446
632
  ];
447
633
  }
448
634
 
449
- return {
450
- ...state,
451
- index: routes.length - 1,
452
- preloadedRoutes: state.preloadedRoutes.filter(
453
- (route) => routes[routes.length - 1].key !== route.key
454
- ),
455
- routes,
456
- };
635
+ return retainRoutes(
636
+ state,
637
+ getStateWithRoutes(
638
+ state,
639
+ nextRoutes,
640
+ preloadedRoutes.filter(
641
+ (route) => nextRoutes[nextRoutes.length - 1].key !== route.key
642
+ )
643
+ )
644
+ );
457
645
  }
458
646
 
459
647
  case 'POP': {
460
648
  let currentIndex =
461
649
  action.target === state.key && action.source
462
- ? state.routes.findIndex((r) => r.key === action.source)
650
+ ? routes.findIndex((r) => r.key === action.source)
463
651
  : state.index;
464
652
 
465
- let route = state.routes[currentIndex];
653
+ if (currentIndex === -1) {
654
+ return null;
655
+ }
656
+
657
+ let route = routes[currentIndex];
466
658
 
467
659
  /**
468
660
  * When popping entries,
@@ -475,11 +667,11 @@ export function StackRouter(options: StackRouterOptions) {
475
667
  */
476
668
  if (route.history?.length || currentIndex > 0) {
477
669
  let count = action.payload.count;
478
- let routes = state.routes;
670
+ let nextRoutes = routes;
479
671
 
480
672
  while (count > 0 && (route.history?.length || currentIndex > 0)) {
481
673
  if (route.history?.length) {
482
- routes = [...routes];
674
+ nextRoutes = [...nextRoutes];
483
675
 
484
676
  const end = Math.min(count, route.history.length);
485
677
  const last = route.history.at(-end);
@@ -488,7 +680,7 @@ export function StackRouter(options: StackRouterOptions) {
488
680
 
489
681
  count = count - removed;
490
682
 
491
- routes[currentIndex] = {
683
+ nextRoutes[currentIndex] = {
492
684
  ...route,
493
685
  params: last?.type === 'params' ? last.params : route.params,
494
686
  history,
@@ -496,33 +688,29 @@ export function StackRouter(options: StackRouterOptions) {
496
688
  }
497
689
 
498
690
  if (currentIndex > 0 && count > 0) {
499
- const length = routes.length;
691
+ const length = nextRoutes.length;
500
692
  const end = Math.max(currentIndex - count + 1, 1);
501
693
 
502
- routes = routes
694
+ nextRoutes = nextRoutes
503
695
  .slice(0, end)
504
- .concat(routes.slice(currentIndex + 1));
696
+ .concat(nextRoutes.slice(currentIndex + 1));
505
697
 
506
- const removed = length - routes.length;
698
+ const removed = length - nextRoutes.length;
507
699
 
508
700
  count = count - removed;
509
- currentIndex = routes.length - 1;
510
- route = routes[currentIndex];
701
+ currentIndex = nextRoutes.length - 1;
702
+ route = nextRoutes[currentIndex];
511
703
  }
512
704
  }
513
705
 
514
- return {
515
- ...state,
516
- index: routes.length - 1,
517
- routes,
518
- };
706
+ return retainRoutes(state, getStateWithRoutes(state, nextRoutes));
519
707
  }
520
708
 
521
709
  return null;
522
710
  }
523
711
 
524
712
  case 'POP_TO_TOP': {
525
- let route = state.routes[0];
713
+ let route = routes[0];
526
714
 
527
715
  if (state.index > 0 || route.history?.length) {
528
716
  if (route.history?.length) {
@@ -532,11 +720,7 @@ export function StackRouter(options: StackRouterOptions) {
532
720
  };
533
721
  }
534
722
 
535
- return {
536
- ...state,
537
- index: 0,
538
- routes: [route],
539
- };
723
+ return retainRoutes(state, getStateWithRoutes(state, [route]));
540
724
  }
541
725
 
542
726
  return null;
@@ -545,7 +729,7 @@ export function StackRouter(options: StackRouterOptions) {
545
729
  case 'POP_TO': {
546
730
  const currentIndex =
547
731
  action.target === state.key && action.source
548
- ? state.routes.findLastIndex((r) => r.key === action.source)
732
+ ? routes.findLastIndex((r) => r.key === action.source)
549
733
  : state.index;
550
734
 
551
735
  if (currentIndex === -1) {
@@ -558,21 +742,46 @@ export function StackRouter(options: StackRouterOptions) {
558
742
 
559
743
  // If the route already exists, navigate to it
560
744
  let index = -1;
745
+ let historyIndex: number | undefined;
561
746
 
562
747
  const getId = options.routeGetIdList[action.payload.name];
563
748
  const id = getId?.({ params: action.payload.params });
564
749
 
565
750
  if (id !== undefined) {
566
- index = state.routes.findIndex(
567
- (route) =>
568
- route.name === action.payload.name &&
569
- id === getId?.({ params: route.params })
570
- );
571
- } else if (state.routes[currentIndex].name === action.payload.name) {
751
+ for (let i = currentIndex; i >= 0; i--) {
752
+ const r = routes[i];
753
+
754
+ if (r.name !== action.payload.name) {
755
+ continue;
756
+ }
757
+
758
+ if (id === getId?.({ params: r.params })) {
759
+ index = i;
760
+ break;
761
+ }
762
+
763
+ if (r.history?.length) {
764
+ for (let j = r.history.length - 1; j >= 0; j--) {
765
+ if (
766
+ r.history[j].type === 'params' &&
767
+ id === getId?.({ params: r.history[j].params })
768
+ ) {
769
+ index = i;
770
+ historyIndex = j;
771
+ break;
772
+ }
773
+ }
774
+
775
+ if (index !== -1) {
776
+ break;
777
+ }
778
+ }
779
+ }
780
+ } else if (routes[currentIndex].name === action.payload.name) {
572
781
  index = currentIndex;
573
782
  } else {
574
783
  for (let i = currentIndex; i >= 0; i--) {
575
- if (state.routes[i].name === action.payload.name) {
784
+ if (routes[i].name === action.payload.name) {
576
785
  index = i;
577
786
  break;
578
787
  }
@@ -581,8 +790,8 @@ export function StackRouter(options: StackRouterOptions) {
581
790
 
582
791
  // If the route doesn't exist, remove the current route and add the new one
583
792
  if (index === -1) {
584
- // Re-use preloaded route if available
585
- let route = state.preloadedRoutes.find(
793
+ // Re-use inactive route if available
794
+ let route = inactiveRoutes.find(
586
795
  (route) =>
587
796
  route.name === action.payload.name &&
588
797
  id === getId?.({ params: route.params })
@@ -590,21 +799,35 @@ export function StackRouter(options: StackRouterOptions) {
590
799
 
591
800
  if (!route) {
592
801
  route = createRouteFromAction({ action, routeParamList });
802
+ } else {
803
+ const params = createParamsFromAction({ action, routeParamList });
804
+
805
+ if (route.params !== params) {
806
+ route = { ...route, params };
807
+ }
593
808
  }
594
809
 
595
- const routes = state.routes.slice(0, currentIndex).concat(route);
810
+ const nextRoutes = routes.slice(0, currentIndex).concat(route);
596
811
 
597
- return {
598
- ...state,
599
- index: routes.length - 1,
600
- routes,
601
- preloadedRoutes: state.preloadedRoutes.filter(
602
- (r) => r.key !== route.key
603
- ),
604
- };
812
+ return retainRoutes(
813
+ state,
814
+ getStateWithRoutes(
815
+ state,
816
+ nextRoutes,
817
+ preloadedRoutes.filter((r) => r.key !== route.key)
818
+ )
819
+ );
605
820
  }
606
821
 
607
- const route = state.routes[index];
822
+ const route = routes[index];
823
+
824
+ let baseParams = route.params;
825
+ let history = route.history;
826
+
827
+ if (historyIndex !== undefined && route.history) {
828
+ baseParams = route.history[historyIndex].params;
829
+ history = route.history.slice(0, historyIndex);
830
+ }
608
831
 
609
832
  let params;
610
833
 
@@ -614,28 +837,27 @@ export function StackRouter(options: StackRouterOptions) {
614
837
  routeParamList[route.name] !== undefined
615
838
  ? {
616
839
  ...routeParamList[route.name],
617
- ...route.params,
840
+ ...baseParams,
618
841
  ...action.payload.params,
619
842
  }
620
- : route.params;
843
+ : baseParams;
621
844
  } else {
622
845
  params = createParamsFromAction({ action, routeParamList });
623
846
  }
624
847
 
625
- return {
626
- ...state,
627
- index,
628
- routes: [
629
- ...state.routes.slice(0, index),
630
- params !== route.params
631
- ? { ...route, params }
632
- : state.routes[index],
633
- ],
634
- };
848
+ return retainRoutes(
849
+ state,
850
+ getStateWithRoutes(state, [
851
+ ...routes.slice(0, index),
852
+ params !== baseParams || historyIndex !== undefined
853
+ ? { ...route, params, history }
854
+ : routes[index],
855
+ ])
856
+ );
635
857
  }
636
858
 
637
859
  case 'GO_BACK':
638
- if (state.index > 0 || state.routes[state.index].history?.length) {
860
+ if (state.index > 0 || routes[state.index].history?.length) {
639
861
  return router.getStateForAction(
640
862
  state,
641
863
  {
@@ -650,45 +872,74 @@ export function StackRouter(options: StackRouterOptions) {
650
872
 
651
873
  return null;
652
874
 
653
- case 'PRELOAD': {
654
- const getId = options.routeGetIdList[action.payload.name];
655
- const id = getId?.({ params: action.payload.params });
875
+ case 'RETAIN': {
876
+ const routeKey = action.source ?? routes[state.index].key;
656
877
 
657
- let route: Route<string> | undefined;
658
-
659
- if (id !== undefined) {
660
- route = state.routes.find(
661
- (route) =>
662
- route.name === action.payload.name &&
663
- id === getId?.({ params: route.params })
664
- );
878
+ if (!state.routes.some((route) => route.key === routeKey)) {
879
+ return null;
665
880
  }
666
881
 
667
- if (route) {
882
+ if (action.payload.enable) {
883
+ if (state.retainedRouteKeys.includes(routeKey)) {
884
+ return state;
885
+ }
886
+
668
887
  return {
669
888
  ...state,
670
- routes: state.routes.map((r) => {
671
- if (r.key !== route?.key) {
672
- return r;
673
- }
674
- return {
675
- ...r,
676
- params: createParamsFromAction({ action, routeParamList }),
677
- };
678
- }),
889
+ retainedRouteKeys: [...state.retainedRouteKeys, routeKey],
679
890
  };
680
- } else {
681
- return {
891
+ }
892
+
893
+ if (!state.retainedRouteKeys.includes(routeKey)) {
894
+ return state;
895
+ }
896
+
897
+ return getStateWithRoutes(
898
+ {
682
899
  ...state,
683
- preloadedRoutes: state.preloadedRoutes
684
- .filter(
685
- (r) =>
686
- r.name !== action.payload.name ||
687
- id !== getId?.({ params: r.params })
688
- )
689
- .concat(createRouteFromAction({ action, routeParamList })),
690
- };
900
+ retainedRouteKeys: state.retainedRouteKeys.filter(
901
+ (key) => key !== routeKey
902
+ ),
903
+ },
904
+ routes,
905
+ preloadedRoutes.filter((route) => route.key !== routeKey)
906
+ );
907
+ }
908
+
909
+ case 'PRELOAD': {
910
+ if (!state.routeNames.includes(action.payload.name)) {
911
+ return null;
691
912
  }
913
+
914
+ const getId = options.routeGetIdList[action.payload.name];
915
+ const id = getId?.({ params: action.payload.params });
916
+ const params = createParamsFromAction({ action, routeParamList });
917
+
918
+ const route = preloadedRoutes.findLast(
919
+ (route) =>
920
+ route.name === action.payload.name &&
921
+ (id === undefined || id === getId?.({ params: route.params }))
922
+ );
923
+
924
+ if (route) {
925
+ return getStateWithRoutes(
926
+ state,
927
+ routes,
928
+ preloadedRoutes.map((r) =>
929
+ r.key === route.key && r.params !== params
930
+ ? { ...r, params }
931
+ : r
932
+ )
933
+ );
934
+ }
935
+
936
+ return getStateWithRoutes(
937
+ state,
938
+ routes,
939
+ preloadedRoutes.concat(
940
+ createRouteFromAction({ action, routeParamList })
941
+ )
942
+ );
692
943
  }
693
944
 
694
945
  default: