@react-navigation/core 8.0.0-alpha.12 → 8.0.0-alpha.14

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.
Files changed (35) hide show
  1. package/lib/module/checkSerializable.js +11 -4
  2. package/lib/module/checkSerializable.js.map +1 -1
  3. package/lib/module/createNavigatorFactory.js.map +1 -1
  4. package/lib/module/getActionFromState.js +15 -2
  5. package/lib/module/getActionFromState.js.map +1 -1
  6. package/lib/module/getStateFromPath.js +61 -45
  7. package/lib/module/getStateFromPath.js.map +1 -1
  8. package/lib/module/index.js +1 -1
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/useEventEmitter.js +43 -31
  11. package/lib/module/useEventEmitter.js.map +1 -1
  12. package/lib/module/useNavigationBuilder.js +49 -48
  13. package/lib/module/useNavigationBuilder.js.map +1 -1
  14. package/lib/module/useRouteCache.js +2 -1
  15. package/lib/module/useRouteCache.js.map +1 -1
  16. package/lib/typescript/src/checkSerializable.d.ts +5 -3
  17. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  18. package/lib/typescript/src/createNavigatorFactory.d.ts +2 -1
  19. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  20. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  21. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  22. package/lib/typescript/src/index.d.ts +1 -1
  23. package/lib/typescript/src/index.d.ts.map +1 -1
  24. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  25. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  26. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  27. package/package.json +3 -3
  28. package/src/checkSerializable.tsx +22 -12
  29. package/src/createNavigatorFactory.tsx +13 -1
  30. package/src/getActionFromState.tsx +20 -4
  31. package/src/getStateFromPath.tsx +110 -70
  32. package/src/index.tsx +1 -1
  33. package/src/useEventEmitter.tsx +45 -36
  34. package/src/useNavigationBuilder.tsx +78 -79
  35. package/src/useRouteCache.tsx +2 -1
@@ -29,11 +29,6 @@ type ParseConfigValue =
29
29
 
30
30
  type ParseConfig = Record<string, ParseConfigValue | undefined>;
31
31
 
32
- type RouteParseConfig = {
33
- parseConfig?: ParseConfig | undefined;
34
- pathParamNames: Set<string>;
35
- };
36
-
37
32
  type RouteConfig = {
38
33
  screen: string;
39
34
  regex?: RegExp | undefined;
@@ -41,6 +36,8 @@ type RouteConfig = {
41
36
  params: { screen: string; name?: string | undefined; index: number }[];
42
37
  routeNames: string[];
43
38
  parse?: ParseConfig | undefined;
39
+ explicitParamNames?: Set<string> | undefined;
40
+ hasNestedScreens: boolean;
44
41
  };
45
42
 
46
43
  type InitialRouteConfig = {
@@ -61,6 +58,7 @@ type ParsedRoute = {
61
58
  type ConfigResources = {
62
59
  initialRoutes: InitialRouteConfig[];
63
60
  configs: RouteConfig[];
61
+ configsByScreen: Record<string, RouteConfig[]>;
64
62
  };
65
63
 
66
64
  const INVALID_SCHEMA_RESULT_ERROR =
@@ -71,6 +69,23 @@ const INVALID_PARSER_ERROR =
71
69
 
72
70
  const PARAM_GROUP_PREFIX = 'param_';
73
71
 
72
+ const NESTED_SCREEN_PARAM_NAMES = [
73
+ 'screen',
74
+ 'params',
75
+ 'initial',
76
+ 'path',
77
+ 'merge',
78
+ 'pop',
79
+ ];
80
+
81
+ const getExplicitParamNames = (parse?: ParseConfig) => {
82
+ const names = Object.entries(parse ?? {})
83
+ .filter(([, parser]) => parser != null)
84
+ .map(([name]) => name);
85
+
86
+ return names.length ? new Set(names) : undefined;
87
+ };
88
+
74
89
  const getStandardSchema = (parser: ParseConfigValue) => {
75
90
  if (
76
91
  '~standard' in parser &&
@@ -129,7 +144,8 @@ export function getStateFromPath<ParamList extends {}>(
129
144
  path: string,
130
145
  options?: Options<ParamList>
131
146
  ): ResultState | undefined {
132
- const { initialRoutes, configs } = getConfigResources(options);
147
+ const { initialRoutes, configs, configsByScreen } =
148
+ getConfigResources(options);
133
149
 
134
150
  const screens = options?.screens;
135
151
 
@@ -156,15 +172,23 @@ export function getStateFromPath<ParamList extends {}>(
156
172
  remaining = remaining.replace(normalizedPrefix, '');
157
173
  }
158
174
 
175
+ const decodedSegments: string[] = [];
176
+
177
+ for (const segment of remaining.split('/')) {
178
+ if (!segment) {
179
+ continue;
180
+ }
181
+
182
+ try {
183
+ decodedSegments.push(decodeURIComponent(segment));
184
+ } catch {
185
+ return undefined;
186
+ }
187
+ }
188
+
159
189
  if (screens === undefined) {
160
190
  // When no config is specified, use the path segments as route names
161
- const routes = remaining
162
- .split('/')
163
- .filter(Boolean)
164
- .map((segment) => {
165
- const name = decodeURIComponent(segment);
166
- return { name };
167
- });
191
+ const routes = decodedSegments.map((name) => ({ name }));
168
192
 
169
193
  if (routes.length) {
170
194
  return createNestedStateObject(path, routes, initialRoutes);
@@ -183,7 +207,7 @@ export function getStateFromPath<ParamList extends {}>(
183
207
  path,
184
208
  match.routeNames.map((name) => ({ name })),
185
209
  initialRoutes,
186
- configs
210
+ match
187
211
  );
188
212
  }
189
213
 
@@ -193,13 +217,13 @@ export function getStateFromPath<ParamList extends {}>(
193
217
  // We match the whole path against the regex instead of segments
194
218
  // This makes sure matches such as wildcard will catch any unmatched routes, even if nested
195
219
  for (const config of configs) {
196
- const routes = matchAgainstConfig(remaining, config, configs);
220
+ const routes = matchAgainstConfig(remaining, config, configsByScreen);
197
221
 
198
222
  if (routes === undefined) {
199
223
  continue;
200
224
  }
201
225
 
202
- const state = createNestedStateObject(path, routes, initialRoutes, configs);
226
+ const state = createNestedStateObject(path, routes, initialRoutes, config);
203
227
 
204
228
  if (state !== undefined) {
205
229
  return state;
@@ -240,12 +264,16 @@ function prepareConfigResources(options?: Options<{}>) {
240
264
 
241
265
  checkForDuplicatedConfigs(configs);
242
266
 
243
- const configWithRegexes = getConfigsWithRegexes(configs);
267
+ const configsByScreen: Record<string, RouteConfig[]> = {};
268
+
269
+ for (const c of configs) {
270
+ (configsByScreen[c.screen] ??= []).push(c);
271
+ }
244
272
 
245
273
  return {
246
274
  initialRoutes,
247
275
  configs,
248
- configWithRegexes,
276
+ configsByScreen,
249
277
  };
250
278
  }
251
279
 
@@ -388,18 +416,10 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
388
416
  }, {});
389
417
  }
390
418
 
391
- function getConfigsWithRegexes(configs: RouteConfig[]) {
392
- return configs.map((c) => ({
393
- ...c,
394
- // Add `$` to the regex to make sure it matches till end of the path and not just beginning
395
- regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
396
- }));
397
- }
398
-
399
419
  const matchAgainstConfig = (
400
420
  remaining: string,
401
421
  config: RouteConfig,
402
- configs: RouteConfig[]
422
+ configsByScreen: Record<string, RouteConfig[]>
403
423
  ) => {
404
424
  if (!config.regex) {
405
425
  return undefined;
@@ -416,9 +436,8 @@ const matchAgainstConfig = (
416
436
 
417
437
  for (const routeName of config.routeNames) {
418
438
  // Check matching name AND pattern in case same screen is used at different levels in config
419
- const routeConfig = configs.find(
420
- (c) =>
421
- c.screen === routeName && arrayStartsWith(config.segments, c.segments)
439
+ const routeConfig = configsByScreen[routeName]?.find((c) =>
440
+ arrayStartsWith(config.segments, c.segments)
422
441
  );
423
442
 
424
443
  let params: Record<string, unknown> | undefined;
@@ -439,7 +458,14 @@ const matchAgainstConfig = (
439
458
  continue;
440
459
  }
441
460
 
442
- const decoded = decodeURIComponent(value);
461
+ let decoded: string;
462
+
463
+ try {
464
+ decoded = decodeURIComponent(value);
465
+ } catch {
466
+ return undefined;
467
+ }
468
+
443
469
  const parser = routeConfig.parse?.[param.name];
444
470
 
445
471
  if (!parser) {
@@ -516,6 +542,9 @@ const createNormalizedConfigs = (
516
542
  // if an object is specified as the value (e.g. Foo: { ... }),
517
543
  // it can have `path` property and
518
544
  // it could have `screens` prop which has nested configs
545
+ const nestedScreens = 'screens' in config ? config.screens : undefined;
546
+ const hasNestedScreens = !!nestedScreens;
547
+
519
548
  if (typeof config.path === 'string') {
520
549
  if (config.exact && config.path == null) {
521
550
  throw new Error(
@@ -535,7 +564,8 @@ const createNormalizedConfigs = (
535
564
  screen,
536
565
  [...routeNames],
537
566
  [...paths, { screen, path: alias }],
538
- config.parse
567
+ config.parse,
568
+ hasNestedScreens
539
569
  )
540
570
  );
541
571
  } else if (typeof alias === 'object') {
@@ -546,7 +576,8 @@ const createNormalizedConfigs = (
546
576
  alias.exact
547
577
  ? [{ screen, path: alias.path }]
548
578
  : [...paths, { screen, path: alias.path }],
549
- alias.parse
579
+ alias.parse,
580
+ hasNestedScreens
550
581
  )
551
582
  );
552
583
  }
@@ -561,7 +592,13 @@ const createNormalizedConfigs = (
561
592
 
562
593
  paths.push({ screen, path: config.path });
563
594
  configs.push(
564
- createConfigItem(screen, [...routeNames], [...paths], config.parse)
595
+ createConfigItem(
596
+ screen,
597
+ [...routeNames],
598
+ [...paths],
599
+ config.parse,
600
+ hasNestedScreens
601
+ )
565
602
  );
566
603
 
567
604
  configs.push(...aliasConfigs);
@@ -577,7 +614,7 @@ const createNormalizedConfigs = (
577
614
  );
578
615
  }
579
616
 
580
- if ('screens' in config && config.screens) {
617
+ if (hasNestedScreens) {
581
618
  // property `initialRouteName` without `screens` has no purpose
582
619
  if (
583
620
  'initialRouteName' in config &&
@@ -589,10 +626,10 @@ const createNormalizedConfigs = (
589
626
  });
590
627
  }
591
628
 
592
- Object.keys(config.screens).forEach((nestedConfig) => {
629
+ Object.keys(nestedScreens).forEach((nestedConfig) => {
593
630
  const result = createNormalizedConfigs(
594
631
  nestedConfig,
595
- config.screens as Record<string, string | PathConfig<{}>>,
632
+ nestedScreens as Record<string, string | PathConfig<{}>>,
596
633
  initials,
597
634
  [...paths],
598
635
  [...parentScreens],
@@ -613,7 +650,8 @@ const createConfigItem = (
613
650
  screen: string,
614
651
  routeNames: string[],
615
652
  paths: { screen: string; path: string }[],
616
- parse?: ParseConfig
653
+ parse?: ParseConfig,
654
+ hasNestedScreens = false
617
655
  ): RouteConfig => {
618
656
  const parts: (PatternPart & { screen: string })[] = [];
619
657
 
@@ -658,34 +696,11 @@ const createConfigItem = (
658
696
  params,
659
697
  routeNames,
660
698
  parse,
699
+ explicitParamNames: getExplicitParamNames(parse),
700
+ hasNestedScreens,
661
701
  };
662
702
  };
663
703
 
664
- const findParseConfigForRoute = (
665
- routeName: string,
666
- flatConfig: RouteConfig[]
667
- ): RouteParseConfig | undefined => {
668
- for (const config of flatConfig) {
669
- if (routeName === config.routeNames[config.routeNames.length - 1]) {
670
- return {
671
- parseConfig: config.parse,
672
- pathParamNames: new Set(
673
- config.params
674
- .filter(
675
- (
676
- param
677
- ): param is { screen: string; name: string; index: number } =>
678
- param.screen === routeName && typeof param.name === 'string'
679
- )
680
- .map((param) => param.name)
681
- ),
682
- };
683
- }
684
- }
685
-
686
- return undefined;
687
- };
688
-
689
704
  // Try to find an initial route connected with the one passed
690
705
  const findInitialRoute = (
691
706
  routeName: string,
@@ -747,7 +762,7 @@ const createNestedStateObject = (
747
762
  path: string,
748
763
  routes: ParsedRoute[],
749
764
  initialRoutes: InitialRouteConfig[],
750
- flatConfig?: RouteConfig[]
765
+ routeConfig?: RouteConfig
751
766
  ): InitialState | undefined => {
752
767
  let route = routes.shift() as ParsedRoute;
753
768
  const parentScreens: string[] = [];
@@ -789,14 +804,22 @@ const createNestedStateObject = (
789
804
  route = findFocusedRoute(state) as ParsedRoute;
790
805
  route.path = path.replace(/\/$/, '');
791
806
 
792
- const parseConfigForRoute = flatConfig
793
- ? findParseConfigForRoute(route.name, flatConfig)
794
- : undefined;
807
+ const pathParamNames = new Set(
808
+ routeConfig?.params
809
+ .filter(
810
+ (param): param is { screen: string; name: string; index: number } =>
811
+ param.screen === routeConfig.screen && typeof param.name === 'string'
812
+ )
813
+ .map((param) => param.name)
814
+ );
795
815
 
796
816
  const queryParams = parseQueryParams(
797
817
  path,
798
- parseConfigForRoute?.parseConfig,
799
- parseConfigForRoute?.pathParamNames
818
+ routeConfig?.parse,
819
+ pathParamNames,
820
+ routeConfig?.explicitParamNames,
821
+ routeConfig?.hasNestedScreens,
822
+ route.params
800
823
  );
801
824
 
802
825
  if (!queryParams.valid) {
@@ -813,7 +836,10 @@ const createNestedStateObject = (
813
836
  const parseQueryParams = (
814
837
  path: string,
815
838
  parseConfig?: ParseConfig,
816
- pathParamNames: Set<string> = new Set()
839
+ pathParamNames: Set<string> = new Set(),
840
+ explicitParamNames?: Set<string>,
841
+ hasNestedScreens = false,
842
+ routeParams?: Record<string, unknown>
817
843
  ):
818
844
  | { valid: true; params?: Record<string, unknown> | undefined }
819
845
  | { valid: false } => {
@@ -882,6 +908,20 @@ const parseQueryParams = (
882
908
  }
883
909
  }
884
910
 
911
+ if (
912
+ hasNestedScreens &&
913
+ !explicitParamNames?.has('screen') &&
914
+ (typeof params.screen === 'string' ||
915
+ typeof routeParams?.screen === 'string')
916
+ ) {
917
+ for (const name of NESTED_SCREEN_PARAM_NAMES) {
918
+ if (!explicitParamNames?.has(name)) {
919
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
920
+ delete params[name];
921
+ }
922
+ }
923
+ }
924
+
885
925
  return {
886
926
  valid: true,
887
927
  params: Object.keys(params).length ? params : undefined,
package/src/index.tsx CHANGED
@@ -38,7 +38,7 @@ export { ThemeProvider } from './theming/ThemeProvider';
38
38
  export { useTheme } from './theming/useTheme';
39
39
  export * from './types';
40
40
  export { useFocusEffect } from './useFocusEffect';
41
- export { useIsFocused } from './useIsFocused';
41
+ export { IsFocusedContext, useIsFocused } from './useIsFocused';
42
42
  export { useNavigation } from './useNavigation';
43
43
  export { useNavigationBuilder } from './useNavigationBuilder';
44
44
  export { useNavigationContainerRef } from './useNavigationContainerRef';
@@ -59,58 +59,67 @@ export function useEventEmitter<T extends Record<string, any>>(
59
59
  target?: string;
60
60
  canPreventDefault?: boolean;
61
61
  }) => {
62
- const items = listeners.current[type] || {};
62
+ const items = listeners.current[type];
63
63
 
64
64
  // Copy the current list of callbacks in case they are mutated during execution
65
- const callbacks = new Set(
66
- Object.values(target ? { [target]: items[target] } : items).flatMap(
67
- (t) => (t ? Array.from(t) : [])
68
- )
69
- );
65
+ let callbacks: Set<(e: any) => void> | undefined;
66
+
67
+ if (items) {
68
+ if (target !== undefined) {
69
+ const targetSet = items[target];
70
+
71
+ if (targetSet && targetSet.size) {
72
+ callbacks = new Set(targetSet);
73
+ }
74
+ } else {
75
+ for (const key in items) {
76
+ const set = items[key];
77
+
78
+ if (set && set.size) {
79
+ callbacks ??= new Set();
80
+
81
+ for (const cb of set) {
82
+ callbacks.add(cb);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
70
88
 
71
- const event: EventArg<any, any, any> = {
72
- get type() {
73
- return type;
74
- },
89
+ const descriptors: PropertyDescriptorMap = {
90
+ type: { enumerable: true, value: type },
75
91
  };
76
92
 
77
93
  if (target !== undefined) {
78
- Object.defineProperty(event, 'target', {
79
- enumerable: true,
80
- get() {
81
- return target;
82
- },
83
- });
94
+ descriptors.target = { enumerable: true, value: target };
84
95
  }
85
96
 
86
97
  if (data !== undefined) {
87
- Object.defineProperty(event, 'data', {
88
- enumerable: true,
89
- get() {
90
- return data;
91
- },
92
- });
98
+ descriptors.data = { enumerable: true, value: data };
93
99
  }
94
100
 
101
+ let defaultPrevented = false;
102
+
95
103
  if (canPreventDefault) {
96
- let defaultPrevented = false;
97
-
98
- Object.defineProperties(event, {
99
- defaultPrevented: {
100
- enumerable: true,
101
- get() {
102
- return defaultPrevented;
103
- },
104
+ descriptors.defaultPrevented = {
105
+ enumerable: true,
106
+ get() {
107
+ return defaultPrevented;
104
108
  },
105
- preventDefault: {
106
- enumerable: true,
107
- value() {
108
- defaultPrevented = true;
109
- },
109
+ };
110
+ descriptors.preventDefault = {
111
+ enumerable: true,
112
+ value() {
113
+ defaultPrevented = true;
110
114
  },
111
- });
115
+ };
112
116
  }
113
117
 
118
+ const event: EventArg<any, any, any> = Object.defineProperties(
119
+ {} as EventArg<any, any, any>,
120
+ descriptors
121
+ );
122
+
114
123
  listenRef.current?.(event);
115
124
 
116
125
  callbacks?.forEach((cb) => cb(event));
@@ -63,6 +63,14 @@ type NavigatorRoute = {
63
63
  params?: NavigatorScreenParams<ParamListBase> | undefined;
64
64
  };
65
65
 
66
+ const isNavigationState = (
67
+ state: unknown
68
+ ): state is NavigationState | PartialState<NavigationState> =>
69
+ state != null &&
70
+ typeof state === 'object' &&
71
+ 'routes' in state &&
72
+ Array.isArray(state.routes);
73
+
66
74
  const isScreen = (
67
75
  child: React.ReactElement<unknown>
68
76
  ): child is React.ReactElement<{
@@ -279,8 +287,10 @@ const getRouteConfigsFromChildren = <
279
287
  };
280
288
 
281
289
  const getStateFromParams = (params: NavigatorRoute['params']) => {
282
- if (params?.state != null) {
283
- return params.state;
290
+ const state = params?.state;
291
+
292
+ if (isNavigationState(state)) {
293
+ return state;
284
294
  } else if (typeof params?.screen === 'string' && params?.initial !== false) {
285
295
  return {
286
296
  routes: [
@@ -373,44 +383,7 @@ export function useNavigationBuilder<
373
383
  return original;
374
384
  });
375
385
 
376
- const screens = routeConfigs.reduce<
377
- Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
378
- >((acc, config) => {
379
- if (config.props.name in acc) {
380
- throw new Error(
381
- `A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.props.name}')`
382
- );
383
- }
384
-
385
- acc[config.props.name] = config;
386
- return acc;
387
- }, {});
388
-
389
386
  const routeNames = routeConfigs.map((config) => config.props.name);
390
- const routeKeyList = routeNames.reduce<Record<string, React.Key | undefined>>(
391
- (acc, curr) => {
392
- acc[curr] = screens[curr].keys.map((key) => key ?? '').join(':');
393
- return acc;
394
- },
395
- {}
396
- );
397
- const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
398
- (acc, curr) => {
399
- const { initialParams } = screens[curr].props;
400
- acc[curr] = initialParams;
401
- return acc;
402
- },
403
- {}
404
- );
405
- const routeGetIdList = routeNames.reduce<
406
- RouterConfigOptions['routeGetIdList']
407
- >(
408
- (acc, curr) =>
409
- Object.assign(acc, {
410
- [curr]: screens[curr].props.getId,
411
- }),
412
- {}
413
- );
414
387
 
415
388
  if (!routeNames.length) {
416
389
  throw new Error(
@@ -418,6 +391,31 @@ export function useNavigationBuilder<
418
391
  );
419
392
  }
420
393
 
394
+ const screens: Record<
395
+ string,
396
+ ScreenConfigWithParent<State, ScreenOptions, EventMap>
397
+ > = {};
398
+
399
+ const routeKeyList: Record<string, React.Key | undefined> = {};
400
+ const routeParamList: Record<string, object | undefined> = {};
401
+ const routeGetIdList: RouterConfigOptions['routeGetIdList'] = {};
402
+
403
+ for (const config of routeConfigs) {
404
+ const name = config.props.name;
405
+
406
+ if (name in screens) {
407
+ throw new Error(
408
+ `A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${name}')`
409
+ );
410
+ }
411
+
412
+ screens[name] = config;
413
+ routeKeyList[name] = config.keys.map((key) => key ?? '').join(':');
414
+ routeParamList[name] = config.props.initialParams;
415
+
416
+ Object.assign(routeGetIdList, { [name]: config.props.getId });
417
+ }
418
+
421
419
  const isStateValid = React.useCallback(
422
420
  (state: NavigationState | PartialState<NavigationState>) =>
423
421
  state.type === undefined || state.type === router.type,
@@ -656,24 +654,21 @@ export function useNavigationBuilder<
656
654
 
657
655
  if (route?.params && !didConsumeNestedParams) {
658
656
  let action: CommonActions.Action | undefined;
657
+ const stateFromParams = route.params.state;
659
658
 
660
- if (
661
- typeof route.params.state === 'object' &&
662
- route.params.state != null &&
663
- !isNestedParamsConsumed
664
- ) {
659
+ if (isNavigationState(stateFromParams) && !isNestedParamsConsumed) {
665
660
  didConsumeNestedParams = true;
666
661
 
667
662
  if (
668
663
  options.routeNamesChangeBehavior === 'lastUnhandled' &&
669
- doesStateHaveOnlyInvalidRoutes(route.params.state)
664
+ doesStateHaveOnlyInvalidRoutes(stateFromParams)
670
665
  ) {
671
- if (route.params.state !== unhandledState) {
672
- setUnhandledState(route.params.state);
666
+ if (stateFromParams !== unhandledState) {
667
+ setUnhandledState(stateFromParams);
673
668
  }
674
669
  } else {
675
670
  // If the route was updated with new state, we should reset to it
676
- action = CommonActions.reset(route.params.state);
671
+ action = CommonActions.reset(stateFromParams);
677
672
  }
678
673
  } else if (
679
674
  typeof route.params.screen === 'string' &&
@@ -834,35 +829,41 @@ export function useNavigationBuilder<
834
829
  return;
835
830
  }
836
831
 
837
- const navigation = descriptors[route.key].navigation;
838
-
839
- const listeners = ([] as (((e: any) => void) | undefined)[])
840
- .concat(
841
- // Get an array of listeners for all screens + common listeners on navigator
842
- ...[
843
- screenListeners,
844
- ...routeNames.map((name) => {
845
- const { listeners } = screens[name].props;
846
- return listeners;
847
- }),
848
- ].map((listeners) => {
849
- const map =
850
- typeof listeners === 'function'
851
- ? listeners({ route: route as any, navigation })
852
- : listeners;
853
-
854
- return map
855
- ? Object.keys(map)
856
- .filter((type) => type === e.type)
857
- .map((type) => map?.[type])
858
- : undefined;
859
- })
860
- )
861
- // We don't want same listener to be called multiple times for same event
862
- // So we remove any duplicate functions from the array
863
- .filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
832
+ const hasPerScreenListeners = routeNames.some(
833
+ (name) => screens[name].props.listeners != null
834
+ );
835
+
836
+ if (screenListeners != null || hasPerScreenListeners) {
837
+ const navigation = descriptors[route.key].navigation;
838
+
839
+ const listeners = ([] as (((e: any) => void) | undefined)[])
840
+ .concat(
841
+ // Get an array of listeners for all screens + common listeners on navigator
842
+ ...[
843
+ screenListeners,
844
+ ...routeNames.map((name) => {
845
+ const { listeners } = screens[name].props;
846
+ return listeners;
847
+ }),
848
+ ].map((listeners) => {
849
+ const map =
850
+ typeof listeners === 'function'
851
+ ? listeners({ route: route as any, navigation })
852
+ : listeners;
853
+
854
+ return map
855
+ ? Object.keys(map)
856
+ .filter((type) => type === e.type)
857
+ .map((type) => map?.[type])
858
+ : undefined;
859
+ })
860
+ )
861
+ // We don't want same listener to be called multiple times for same event
862
+ // So we remove any duplicate functions from the array
863
+ .filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
864
864
 
865
- listeners.forEach((listener) => listener?.(e));
865
+ listeners.forEach((listener) => listener?.(e));
866
+ }
866
867
 
867
868
  onEmitEvent({
868
869
  type: e.type,
@@ -970,9 +971,7 @@ export function useNavigationBuilder<
970
971
  ScreenOptions,
971
972
  EventMap
972
973
  >({
973
- routes: router.getRoutesFromState
974
- ? router.getRoutesFromState(state)
975
- : state.routes,
974
+ routes: state.routes,
976
975
  screens,
977
976
  navigation,
978
977
  screenOptions,