@react-navigation/core 6.0.1 → 6.1.1

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 (147) hide show
  1. package/lib/commonjs/BaseNavigationContainer.js +17 -12
  2. package/lib/commonjs/BaseNavigationContainer.js.map +1 -1
  3. package/lib/commonjs/CurrentRenderContext.js.map +1 -1
  4. package/lib/commonjs/EnsureSingleNavigator.js +5 -4
  5. package/lib/commonjs/EnsureSingleNavigator.js.map +1 -1
  6. package/lib/commonjs/NavigationContainerRefContext.js.map +1 -1
  7. package/lib/commonjs/NavigationContext.js.map +1 -1
  8. package/lib/commonjs/NavigationHelpersContext.js.map +1 -1
  9. package/lib/commonjs/NavigationRouteContext.js.map +1 -1
  10. package/lib/commonjs/SceneView.js +11 -10
  11. package/lib/commonjs/SceneView.js.map +1 -1
  12. package/lib/commonjs/UnhandledActionContext.js.map +1 -1
  13. package/lib/commonjs/createNavigationContainerRef.js +64 -16
  14. package/lib/commonjs/createNavigationContainerRef.js.map +1 -1
  15. package/lib/commonjs/createNavigatorFactory.js +1 -1
  16. package/lib/commonjs/createNavigatorFactory.js.map +1 -1
  17. package/lib/commonjs/fromEntries.js +3 -1
  18. package/lib/commonjs/fromEntries.js.map +1 -1
  19. package/lib/commonjs/getActionFromState.js +2 -1
  20. package/lib/commonjs/getActionFromState.js.map +1 -1
  21. package/lib/commonjs/getPathFromState.js +13 -3
  22. package/lib/commonjs/getPathFromState.js.map +1 -1
  23. package/lib/commonjs/getStateFromPath.js +12 -2
  24. package/lib/commonjs/getStateFromPath.js.map +1 -1
  25. package/lib/commonjs/index.js +24 -24
  26. package/lib/commonjs/isArrayEqual.js +9 -1
  27. package/lib/commonjs/isArrayEqual.js.map +1 -1
  28. package/lib/commonjs/isRecordEqual.js +25 -0
  29. package/lib/commonjs/isRecordEqual.js.map +1 -0
  30. package/lib/commonjs/types.js.map +1 -1
  31. package/lib/commonjs/useChildListeners.js.map +1 -1
  32. package/lib/commonjs/useCurrentRender.js +6 -5
  33. package/lib/commonjs/useCurrentRender.js.map +1 -1
  34. package/lib/commonjs/useDescriptors.js +18 -17
  35. package/lib/commonjs/useDescriptors.js.map +1 -1
  36. package/lib/commonjs/useEventEmitter.js +7 -6
  37. package/lib/commonjs/useEventEmitter.js.map +1 -1
  38. package/lib/commonjs/useFocusEvents.js +5 -4
  39. package/lib/commonjs/useFocusEvents.js.map +1 -1
  40. package/lib/commonjs/useFocusedListenersChildrenAdapter.js +5 -4
  41. package/lib/commonjs/useFocusedListenersChildrenAdapter.js.map +1 -1
  42. package/lib/commonjs/useKeyedChildListeners.js.map +1 -1
  43. package/lib/commonjs/useNavigationBuilder.js +51 -20
  44. package/lib/commonjs/useNavigationBuilder.js.map +1 -1
  45. package/lib/commonjs/useNavigationCache.js +17 -10
  46. package/lib/commonjs/useNavigationCache.js.map +1 -1
  47. package/lib/commonjs/useNavigationHelpers.js +10 -7
  48. package/lib/commonjs/useNavigationHelpers.js.map +1 -1
  49. package/lib/commonjs/useOnAction.js +13 -11
  50. package/lib/commonjs/useOnAction.js.map +1 -1
  51. package/lib/commonjs/useOnGetState.js +5 -4
  52. package/lib/commonjs/useOnGetState.js.map +1 -1
  53. package/lib/commonjs/useOnPreventRemove.js +6 -5
  54. package/lib/commonjs/useOnPreventRemove.js.map +1 -1
  55. package/lib/commonjs/useOnRouteFocus.js +7 -6
  56. package/lib/commonjs/useOnRouteFocus.js.map +1 -1
  57. package/lib/commonjs/useOptionsGetters.js +6 -5
  58. package/lib/commonjs/useOptionsGetters.js.map +1 -1
  59. package/lib/commonjs/useRegisterNavigator.js +1 -1
  60. package/lib/commonjs/useRegisterNavigator.js.map +1 -1
  61. package/lib/commonjs/useRouteCache.js +1 -1
  62. package/lib/commonjs/useScheduleUpdate.js +1 -1
  63. package/lib/commonjs/validatePathConfig.js +5 -2
  64. package/lib/commonjs/validatePathConfig.js.map +1 -1
  65. package/lib/module/BaseNavigationContainer.js +17 -12
  66. package/lib/module/BaseNavigationContainer.js.map +1 -1
  67. package/lib/module/CurrentRenderContext.js.map +1 -1
  68. package/lib/module/EnsureSingleNavigator.js +4 -3
  69. package/lib/module/EnsureSingleNavigator.js.map +1 -1
  70. package/lib/module/NavigationContainerRefContext.js.map +1 -1
  71. package/lib/module/NavigationContext.js.map +1 -1
  72. package/lib/module/NavigationHelpersContext.js.map +1 -1
  73. package/lib/module/NavigationRouteContext.js.map +1 -1
  74. package/lib/module/SceneView.js +11 -10
  75. package/lib/module/SceneView.js.map +1 -1
  76. package/lib/module/UnhandledActionContext.js.map +1 -1
  77. package/lib/module/createNavigationContainerRef.js +63 -15
  78. package/lib/module/createNavigationContainerRef.js.map +1 -1
  79. package/lib/module/createNavigatorFactory.js +1 -1
  80. package/lib/module/createNavigatorFactory.js.map +1 -1
  81. package/lib/module/fromEntries.js +3 -1
  82. package/lib/module/fromEntries.js.map +1 -1
  83. package/lib/module/getActionFromState.js +2 -1
  84. package/lib/module/getActionFromState.js.map +1 -1
  85. package/lib/module/getPathFromState.js +13 -3
  86. package/lib/module/getPathFromState.js.map +1 -1
  87. package/lib/module/getStateFromPath.js +12 -2
  88. package/lib/module/getStateFromPath.js.map +1 -1
  89. package/lib/module/isArrayEqual.js +9 -1
  90. package/lib/module/isArrayEqual.js.map +1 -1
  91. package/lib/module/isRecordEqual.js +18 -0
  92. package/lib/module/isRecordEqual.js.map +1 -0
  93. package/lib/module/types.js.map +1 -1
  94. package/lib/module/useChildListeners.js.map +1 -1
  95. package/lib/module/useCurrentRender.js +6 -5
  96. package/lib/module/useCurrentRender.js.map +1 -1
  97. package/lib/module/useDescriptors.js +18 -17
  98. package/lib/module/useDescriptors.js.map +1 -1
  99. package/lib/module/useEventEmitter.js +7 -6
  100. package/lib/module/useEventEmitter.js.map +1 -1
  101. package/lib/module/useFocusEvents.js +5 -4
  102. package/lib/module/useFocusEvents.js.map +1 -1
  103. package/lib/module/useFocusedListenersChildrenAdapter.js +5 -4
  104. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  105. package/lib/module/useKeyedChildListeners.js.map +1 -1
  106. package/lib/module/useNavigationBuilder.js +50 -20
  107. package/lib/module/useNavigationBuilder.js.map +1 -1
  108. package/lib/module/useNavigationCache.js +17 -10
  109. package/lib/module/useNavigationCache.js.map +1 -1
  110. package/lib/module/useNavigationHelpers.js +10 -7
  111. package/lib/module/useNavigationHelpers.js.map +1 -1
  112. package/lib/module/useOnAction.js +13 -11
  113. package/lib/module/useOnAction.js.map +1 -1
  114. package/lib/module/useOnGetState.js +5 -4
  115. package/lib/module/useOnGetState.js.map +1 -1
  116. package/lib/module/useOnPreventRemove.js +6 -5
  117. package/lib/module/useOnPreventRemove.js.map +1 -1
  118. package/lib/module/useOnRouteFocus.js +7 -6
  119. package/lib/module/useOnRouteFocus.js.map +1 -1
  120. package/lib/module/useOptionsGetters.js +6 -5
  121. package/lib/module/useOptionsGetters.js.map +1 -1
  122. package/lib/module/useRegisterNavigator.js +1 -1
  123. package/lib/module/useRegisterNavigator.js.map +1 -1
  124. package/lib/module/validatePathConfig.js +5 -2
  125. package/lib/module/validatePathConfig.js.map +1 -1
  126. package/lib/typescript/src/isRecordEqual.d.ts +4 -0
  127. package/lib/typescript/src/types.d.ts +11 -0
  128. package/lib/typescript/src/useDescriptors.d.ts +5 -4
  129. package/package.json +5 -5
  130. package/src/BaseNavigationContainer.tsx +4 -3
  131. package/src/CurrentRenderContext.tsx +3 -2
  132. package/src/EnsureSingleNavigator.tsx +7 -8
  133. package/src/NavigationContainerRefContext.tsx +3 -4
  134. package/src/NavigationContext.tsx +3 -2
  135. package/src/NavigationHelpersContext.tsx +3 -2
  136. package/src/NavigationRouteContext.tsx +3 -2
  137. package/src/UnhandledActionContext.tsx +3 -4
  138. package/src/createNavigationContainerRef.tsx +63 -12
  139. package/src/createNavigatorFactory.tsx +1 -1
  140. package/src/isArrayEqual.tsx +9 -1
  141. package/src/isRecordEqual.tsx +20 -0
  142. package/src/types.tsx +19 -6
  143. package/src/useChildListeners.tsx +3 -5
  144. package/src/useDescriptors.tsx +8 -7
  145. package/src/useKeyedChildListeners.tsx +6 -8
  146. package/src/useNavigationBuilder.tsx +89 -27
  147. package/src/useRegisterNavigator.tsx +1 -1
@@ -1,9 +1,8 @@
1
1
  import type { NavigationAction } from '@react-navigation/routers';
2
2
  import * as React from 'react';
3
3
 
4
- const UnhandledActionContext =
5
- React.createContext<((action: NavigationAction) => void) | undefined>(
6
- undefined
7
- );
4
+ const UnhandledActionContext = React.createContext<
5
+ ((action: NavigationAction) => void) | undefined
6
+ >(undefined);
8
7
 
9
8
  export default UnhandledActionContext;
@@ -1,6 +1,10 @@
1
1
  import { CommonActions } from '@react-navigation/routers';
2
2
 
3
- import type { NavigationContainerRefWithCurrent } from './types';
3
+ import type {
4
+ NavigationContainerEventMap,
5
+ NavigationContainerRef,
6
+ NavigationContainerRefWithCurrent,
7
+ } from './types';
4
8
 
5
9
  export const NOT_INITIALIZED_ERROR =
6
10
  "The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
@@ -14,6 +18,7 @@ export default function createNavigationContainerRef<
14
18
  'removeListener',
15
19
  'resetRoot',
16
20
  'dispatch',
21
+ 'isFocused',
17
22
  'canGoBack',
18
23
  'getRootState',
19
24
  'getState',
@@ -22,26 +27,72 @@ export default function createNavigationContainerRef<
22
27
  'getCurrentOptions',
23
28
  ] as const;
24
29
 
30
+ const listeners: Record<string, ((...args: any[]) => void)[]> = {};
31
+
32
+ const removeListener = (
33
+ event: string,
34
+ callback: (...args: any[]) => void
35
+ ) => {
36
+ if (listeners[event]) {
37
+ listeners[event] = listeners[event].filter((cb) => cb !== callback);
38
+ }
39
+ };
40
+
41
+ let current: NavigationContainerRef<ParamList> | null = null;
42
+
25
43
  const ref: NavigationContainerRefWithCurrent<ParamList> = {
44
+ get current() {
45
+ return current;
46
+ },
47
+ set current(value: NavigationContainerRef<ParamList> | null) {
48
+ current = value;
49
+
50
+ if (value != null) {
51
+ Object.entries(listeners).forEach(([event, callbacks]) => {
52
+ callbacks.forEach((callback) => {
53
+ value.addListener(
54
+ event as keyof NavigationContainerEventMap,
55
+ callback
56
+ );
57
+ });
58
+ });
59
+ }
60
+ },
61
+ isReady: () => {
62
+ if (current == null) {
63
+ return false;
64
+ }
65
+
66
+ return current.isReady();
67
+ },
26
68
  ...methods.reduce<any>((acc, name) => {
27
69
  acc[name] = (...args: any[]) => {
28
- if (ref.current == null) {
29
- console.error(NOT_INITIALIZED_ERROR);
70
+ if (current == null) {
71
+ switch (name) {
72
+ case 'addListener': {
73
+ const [event, callback] = args;
74
+
75
+ listeners[event] = listeners[event] || [];
76
+ listeners[event].push(callback);
77
+
78
+ return () => removeListener(event, callback);
79
+ }
80
+ case 'removeListener': {
81
+ const [event, callback] = args;
82
+
83
+ removeListener(event, callback);
84
+ break;
85
+ }
86
+ default:
87
+ console.error(NOT_INITIALIZED_ERROR);
88
+ }
30
89
  } else {
31
90
  // @ts-expect-error: this is ok
32
- return ref.current[name](...args);
91
+ return current[name](...args);
33
92
  }
34
93
  };
35
94
  return acc;
36
95
  }, {}),
37
- isReady: () => {
38
- if (ref.current == null) {
39
- return false;
40
- }
41
-
42
- return ref.current.isReady();
43
- },
44
- current: null,
45
96
  };
46
97
 
47
98
  return ref;
@@ -27,7 +27,7 @@ export default function createNavigatorFactory<
27
27
  > {
28
28
  if (arguments[0] !== undefined) {
29
29
  throw new Error(
30
- "Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5? See https://reactnavigation.org/docs/upgrading-from-4.x for migration guide."
30
+ "Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API? See https://reactnavigation.org/docs/hello-react-navigation for the latest API and guides."
31
31
  );
32
32
  }
33
33
 
@@ -3,5 +3,13 @@
3
3
  * We need to make sure that both values and order match.
4
4
  */
5
5
  export default function isArrayEqual(a: any[], b: any[]) {
6
- return a.length === b.length && a.every((it, index) => it === b[index]);
6
+ if (a === b) {
7
+ return true;
8
+ }
9
+
10
+ if (a.length !== b.length) {
11
+ return false;
12
+ }
13
+
14
+ return a.every((it, index) => it === b[index]);
7
15
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Compare two records with primitive values as the content.
3
+ */
4
+ export default function isRecordEqual(
5
+ a: Record<string, any>,
6
+ b: Record<string, any>
7
+ ) {
8
+ if (a === b) {
9
+ return true;
10
+ }
11
+
12
+ const aKeys = Object.keys(a);
13
+ const bKeys = Object.keys(b);
14
+
15
+ if (aKeys.length !== bKeys.length) {
16
+ return false;
17
+ }
18
+
19
+ return aKeys.every((key) => a[key] === b[key]);
20
+ }
package/src/types.tsx CHANGED
@@ -393,12 +393,12 @@ export type Descriptor<
393
393
  export type ScreenListeners<
394
394
  State extends NavigationState,
395
395
  EventMap extends EventMapBase
396
- > = Partial<
397
- {
398
- [EventName in keyof (EventMap &
399
- EventMapCore<State>)]: EventListenerCallback<EventMap, EventName>;
400
- }
401
- >;
396
+ > = Partial<{
397
+ [EventName in keyof (EventMap & EventMapCore<State>)]: EventListenerCallback<
398
+ EventMap,
399
+ EventName
400
+ >;
401
+ }>;
402
402
 
403
403
  export type RouteConfigComponent<
404
404
  ParamList extends ParamListBase,
@@ -439,6 +439,13 @@ export type RouteConfig<
439
439
  ScreenOptions extends {},
440
440
  EventMap extends EventMapBase
441
441
  > = {
442
+ /**
443
+ * Optional key for this screen. This doesn't need to be unique.
444
+ * If the key changes, existing screens with this name will be removed or reset.
445
+ * Useful when we have some common screens and have conditional rendering.
446
+ */
447
+ navigationKey?: string;
448
+
442
449
  /**
443
450
  * Route name of this screen.
444
451
  */
@@ -482,6 +489,12 @@ export type RouteGroupConfig<
482
489
  ParamList extends ParamListBase,
483
490
  ScreenOptions extends {}
484
491
  > = {
492
+ /**
493
+ * Optional key for the screens in this group.
494
+ * If the key changes, all existing screens in this group will be removed or reset.
495
+ */
496
+ navigationKey?: string;
497
+
485
498
  /**
486
499
  * Navigator options for this screen.
487
500
  */
@@ -6,11 +6,9 @@ import type { ListenerMap } from './NavigationBuilderContext';
6
6
  * Hook which lets child navigators add action listeners.
7
7
  */
8
8
  export default function useChildListeners() {
9
- const { current: listeners } = React.useRef<
10
- {
11
- [K in keyof ListenerMap]: ListenerMap[K][];
12
- }
13
- >({
9
+ const { current: listeners } = React.useRef<{
10
+ [K in keyof ListenerMap]: ListenerMap[K][];
11
+ }>({
14
12
  action: [],
15
13
  focus: [],
16
14
  });
@@ -29,10 +29,11 @@ export type ScreenConfigWithParent<
29
29
  State extends NavigationState,
30
30
  ScreenOptions extends {},
31
31
  EventMap extends EventMapBase
32
- > = [
33
- (ScreenOptionsOrCallback<ScreenOptions> | undefined)[] | undefined,
34
- RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
35
- ];
32
+ > = {
33
+ keys: (string | undefined)[];
34
+ options: (ScreenOptionsOrCallback<ScreenOptions> | undefined)[] | undefined;
35
+ props: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
36
+ };
36
37
 
37
38
  type ScreenOptionsOrCallback<ScreenOptions extends {}> =
38
39
  | ScreenOptions
@@ -149,15 +150,15 @@ export default function useDescriptors<
149
150
  >
150
151
  >((acc, route, i) => {
151
152
  const config = screens[route.name];
152
- const screen = config[1];
153
+ const screen = config.props;
153
154
  const navigation = navigations[route.key];
154
155
 
155
156
  const optionsList = [
156
157
  // The default `screenOptions` passed to the navigator
157
158
  screenOptions,
158
159
  // The `screenOptions` props passed to `Group` elements
159
- ...((config[0]
160
- ? config[0].filter(Boolean)
160
+ ...((config.options
161
+ ? config.options.filter(Boolean)
161
162
  : []) as ScreenOptionsOrCallback<ScreenOptions>[]),
162
163
  // The `options` prop passed to `Screen` elements,
163
164
  screen.options,
@@ -6,14 +6,12 @@ import type { KeyedListenerMap } from './NavigationBuilderContext';
6
6
  * Hook which lets child navigators add getters to be called for obtaining rehydrated state.
7
7
  */
8
8
  export default function useKeyedChildListeners() {
9
- const { current: keyedListeners } = React.useRef<
10
- {
11
- [K in keyof KeyedListenerMap]: Record<
12
- string,
13
- KeyedListenerMap[K] | undefined
14
- >;
15
- }
16
- >({
9
+ const { current: keyedListeners } = React.useRef<{
10
+ [K in keyof KeyedListenerMap]: Record<
11
+ string,
12
+ KeyedListenerMap[K] | undefined
13
+ >;
14
+ }>({
17
15
  getState: {},
18
16
  beforeRemove: {},
19
17
  });
@@ -15,6 +15,7 @@ import { isValidElementType } from 'react-is';
15
15
 
16
16
  import Group from './Group';
17
17
  import isArrayEqual from './isArrayEqual';
18
+ import isRecordEqual from './isRecordEqual';
18
19
  import NavigationHelpersContext from './NavigationHelpersContext';
19
20
  import NavigationRouteContext from './NavigationRouteContext';
20
21
  import NavigationStateContext from './NavigationStateContext';
@@ -51,6 +52,9 @@ type NavigatorRoute<State extends NavigationState> = {
51
52
  params?: NavigatorScreenParams<ParamListBase, State>;
52
53
  };
53
54
 
55
+ const isValidKey = (key: unknown) =>
56
+ key === undefined || (typeof key === 'string' && key !== '');
57
+
54
58
  /**
55
59
  * Extract route config object from React children elements.
56
60
  *
@@ -62,7 +66,12 @@ const getRouteConfigsFromChildren = <
62
66
  EventMap extends EventMapBase
63
67
  >(
64
68
  children: React.ReactNode,
65
- options?: ScreenConfigWithParent<State, ScreenOptions, EventMap>[0]
69
+ groupKey?: string,
70
+ groupOptions?: ScreenConfigWithParent<
71
+ State,
72
+ ScreenOptions,
73
+ EventMap
74
+ >['options']
66
75
  ) => {
67
76
  const configs = React.Children.toArray(children).reduce<
68
77
  ScreenConfigWithParent<State, ScreenOptions, EventMap>[]
@@ -71,29 +80,50 @@ const getRouteConfigsFromChildren = <
71
80
  if (child.type === Screen) {
72
81
  // We can only extract the config from `Screen` elements
73
82
  // If something else was rendered, it's probably a bug
74
- acc.push([
75
- options,
76
- child.props as RouteConfig<
83
+
84
+ if (!isValidKey(child.props.navigationKey)) {
85
+ throw new Error(
86
+ `Got an invalid 'navigationKey' prop (${JSON.stringify(
87
+ child.props.navigationKey
88
+ )}) for the screen '${
89
+ child.props.name
90
+ }'. It must be a non-empty string or 'undefined'.`
91
+ );
92
+ }
93
+
94
+ acc.push({
95
+ keys: [groupKey, child.props.navigationKey],
96
+ options: groupOptions,
97
+ props: child.props as RouteConfig<
77
98
  ParamListBase,
78
99
  string,
79
100
  State,
80
101
  ScreenOptions,
81
102
  EventMap
82
103
  >,
83
- ]);
104
+ });
84
105
  return acc;
85
106
  }
86
107
 
87
108
  if (child.type === React.Fragment || child.type === Group) {
109
+ if (!isValidKey(child.props.navigationKey)) {
110
+ throw new Error(
111
+ `Got an invalid 'navigationKey' prop (${JSON.stringify(
112
+ child.props.navigationKey
113
+ )}) for the group. It must be a non-empty string or 'undefined'.`
114
+ );
115
+ }
116
+
88
117
  // When we encounter a fragment or group, we need to dive into its children to extract the configs
89
118
  // This is handy to conditionally define a group of screens
90
119
  acc.push(
91
120
  ...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
92
121
  child.props.children,
122
+ child.props.navigationKey,
93
123
  child.type !== Group
94
- ? options
95
- : options != null
96
- ? [...options, child.props.screenOptions]
124
+ ? groupOptions
125
+ : groupOptions != null
126
+ ? [...groupOptions, child.props.screenOptions]
97
127
  : [child.props.screenOptions]
98
128
  )
99
129
  );
@@ -118,7 +148,7 @@ const getRouteConfigsFromChildren = <
118
148
 
119
149
  if (process.env.NODE_ENV !== 'production') {
120
150
  configs.forEach((config) => {
121
- const { name, children, component, getComponent } = config[1];
151
+ const { name, children, component, getComponent } = config.props;
122
152
 
123
153
  if (typeof name !== 'string' || !name) {
124
154
  throw new Error(
@@ -169,13 +199,19 @@ const getRouteConfigsFromChildren = <
169
199
  );
170
200
  }
171
201
 
172
- if (typeof component === 'function' && component.name === 'component') {
173
- // Inline anonymous functions passed in the `component` prop will have the name of the prop
174
- // It's relatively safe to assume that it's not a component since it should also have PascalCase name
175
- // We won't catch all scenarios here, but this should catch a good chunk of incorrect use.
176
- console.warn(
177
- `Looks like you're passing an inline function for 'component' prop for the screen '${name}' (e.g. component={() => <SomeComponent />}). Passing an inline function will cause the component state to be lost on re-render and cause perf issues since it's re-created every render. You can pass the function as children to 'Screen' instead to achieve the desired behaviour.`
178
- );
202
+ if (typeof component === 'function') {
203
+ if (component.name === 'component') {
204
+ // Inline anonymous functions passed in the `component` prop will have the name of the prop
205
+ // It's relatively safe to assume that it's not a component since it should also have PascalCase name
206
+ // We won't catch all scenarios here, but this should catch a good chunk of incorrect use.
207
+ console.warn(
208
+ `Looks like you're passing an inline function for 'component' prop for the screen '${name}' (e.g. component={() => <SomeComponent />}). Passing an inline function will cause the component state to be lost on re-render and cause perf issues since it's re-created every render. You can pass the function as children to 'Screen' instead to achieve the desired behaviour.`
209
+ );
210
+ } else if (/^[a-z]/.test(component.name)) {
211
+ console.warn(
212
+ `Got a component with the name '${component.name}' for the screen '${name}'. React Components must start with an uppercase letter. If you're passing a regular function and not a component, pass it as children to 'Screen' instead. Otherwise capitalize your component's name.`
213
+ );
214
+ }
179
215
  }
180
216
  } else {
181
217
  throw new Error(
@@ -230,26 +266,36 @@ export default function useNavigationBuilder<
230
266
  })
231
267
  );
232
268
 
233
- const routeConfigs =
234
- getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(children);
269
+ const routeConfigs = getRouteConfigsFromChildren<
270
+ State,
271
+ ScreenOptions,
272
+ EventMap
273
+ >(children);
235
274
 
236
275
  const screens = routeConfigs.reduce<
237
276
  Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
238
277
  >((acc, config) => {
239
- if (config[1].name in acc) {
278
+ if (config.props.name in acc) {
240
279
  throw new Error(
241
- `A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config[1].name}')`
280
+ `A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.props.name}')`
242
281
  );
243
282
  }
244
283
 
245
- acc[config[1].name] = config;
284
+ acc[config.props.name] = config;
246
285
  return acc;
247
286
  }, {});
248
287
 
249
- const routeNames = routeConfigs.map((config) => config[1].name);
288
+ const routeNames = routeConfigs.map((config) => config.props.name);
289
+ const routeKeyList = routeNames.reduce<Record<string, React.Key | undefined>>(
290
+ (acc, curr) => {
291
+ acc[curr] = screens[curr].keys.map((key) => key ?? '').join(':');
292
+ return acc;
293
+ },
294
+ {}
295
+ );
250
296
  const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
251
297
  (acc, curr) => {
252
- const { initialParams } = screens[curr][1];
298
+ const { initialParams } = screens[curr].props;
253
299
  acc[curr] = initialParams;
254
300
  return acc;
255
301
  },
@@ -260,7 +306,7 @@ export default function useNavigationBuilder<
260
306
  >(
261
307
  (acc, curr) =>
262
308
  Object.assign(acc, {
263
- [curr]: screens[curr][1].getId,
309
+ [curr]: screens[curr].props.getId,
264
310
  }),
265
311
  {}
266
312
  );
@@ -315,7 +361,7 @@ export default function useNavigationBuilder<
315
361
  const initialRouteParamList = routeNames.reduce<
316
362
  Record<string, object | undefined>
317
363
  >((acc, curr) => {
318
- const { initialParams } = screens[curr][1];
364
+ const { initialParams } = screens[curr].props;
319
365
  const initialParamsFromParams =
320
366
  route?.params?.state == null &&
321
367
  route?.params?.initial !== false &&
@@ -371,6 +417,14 @@ export default function useNavigationBuilder<
371
417
  // eslint-disable-next-line react-hooks/exhaustive-deps
372
418
  }, [currentState, router, isStateValid]);
373
419
 
420
+ const previousRouteKeyListRef = React.useRef(routeKeyList);
421
+
422
+ React.useEffect(() => {
423
+ previousRouteKeyListRef.current = routeKeyList;
424
+ });
425
+
426
+ const previousRouteKeyList = previousRouteKeyListRef.current;
427
+
374
428
  let state =
375
429
  // If the state isn't initialized, or stale, use the state we initialized instead
376
430
  // The state won't update until there's a change needed in the state we have initalized locally
@@ -381,12 +435,20 @@ export default function useNavigationBuilder<
381
435
 
382
436
  let nextState: State = state;
383
437
 
384
- if (!isArrayEqual(state.routeNames, routeNames)) {
438
+ if (
439
+ !isArrayEqual(state.routeNames, routeNames) ||
440
+ !isRecordEqual(routeKeyList, previousRouteKeyList)
441
+ ) {
385
442
  // When the list of route names change, the router should handle it to remove invalid routes
386
443
  nextState = router.getStateForRouteNamesChange(state, {
387
444
  routeNames,
388
445
  routeParamList,
389
446
  routeGetIdList,
447
+ routeKeyChanges: Object.keys(routeKeyList).filter(
448
+ (name) =>
449
+ previousRouteKeyList.hasOwnProperty(name) &&
450
+ routeKeyList[name] !== previousRouteKeyList[name]
451
+ ),
390
452
  });
391
453
  }
392
454
 
@@ -522,7 +584,7 @@ export default function useNavigationBuilder<
522
584
  ...[
523
585
  screenListeners,
524
586
  ...routeNames.map((name) => {
525
- const { listeners } = screens[name][1];
587
+ const { listeners } = screens[name].props;
526
588
  return listeners;
527
589
  }),
528
590
  ].map((listeners) => {
@@ -13,7 +13,7 @@ export default function useRegisterNavigator() {
13
13
 
14
14
  if (container === undefined) {
15
15
  throw new Error(
16
- "Couldn't register the navigator. Have you wrapped your app with 'NavigationContainer'?"
16
+ "Couldn't register the navigator. Have you wrapped your app with 'NavigationContainer'?\n\nThis can also happen if there are multiple copies of '@react-navigation' packages installed."
17
17
  );
18
18
  }
19
19