@react-navigation/core 6.2.0 → 6.3.0

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 (81) hide show
  1. package/README.md +17 -11
  2. package/lib/commonjs/PreventRemoveContext.js +17 -0
  3. package/lib/commonjs/PreventRemoveContext.js.map +1 -0
  4. package/lib/commonjs/PreventRemoveProvider.js +114 -0
  5. package/lib/commonjs/PreventRemoveProvider.js.map +1 -0
  6. package/lib/commonjs/getStateFromPath.js +1 -1
  7. package/lib/commonjs/getStateFromPath.js.map +1 -1
  8. package/lib/commonjs/index.js +36 -0
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/types.js.map +1 -1
  11. package/lib/commonjs/useChildListeners.js +6 -3
  12. package/lib/commonjs/useChildListeners.js.map +1 -1
  13. package/lib/commonjs/useComponent.js +22 -12
  14. package/lib/commonjs/useComponent.js.map +1 -1
  15. package/lib/commonjs/useEventEmitter.js +13 -3
  16. package/lib/commonjs/useEventEmitter.js.map +1 -1
  17. package/lib/commonjs/useFocusEffect.js +1 -1
  18. package/lib/commonjs/useFocusEffect.js.map +1 -1
  19. package/lib/commonjs/useKeyedChildListeners.js +4 -2
  20. package/lib/commonjs/useKeyedChildListeners.js.map +1 -1
  21. package/lib/commonjs/useNavigationBuilder.js +4 -2
  22. package/lib/commonjs/useNavigationBuilder.js.map +1 -1
  23. package/lib/commonjs/useNavigationHelpers.js +0 -4
  24. package/lib/commonjs/useNavigationHelpers.js.map +1 -1
  25. package/lib/commonjs/usePreventRemove.js +59 -0
  26. package/lib/commonjs/usePreventRemove.js.map +1 -0
  27. package/lib/commonjs/usePreventRemoveContext.js +27 -0
  28. package/lib/commonjs/usePreventRemoveContext.js.map +1 -0
  29. package/lib/module/PreventRemoveContext.js +9 -0
  30. package/lib/module/PreventRemoveContext.js.map +1 -0
  31. package/lib/module/PreventRemoveProvider.js +96 -0
  32. package/lib/module/PreventRemoveProvider.js.map +1 -0
  33. package/lib/module/getStateFromPath.js +1 -1
  34. package/lib/module/getStateFromPath.js.map +1 -1
  35. package/lib/module/index.js +4 -0
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/module/types.js.map +1 -1
  38. package/lib/module/useChildListeners.js +6 -3
  39. package/lib/module/useChildListeners.js.map +1 -1
  40. package/lib/module/useComponent.js +23 -12
  41. package/lib/module/useComponent.js.map +1 -1
  42. package/lib/module/useEventEmitter.js +13 -3
  43. package/lib/module/useEventEmitter.js.map +1 -1
  44. package/lib/module/useFocusEffect.js +1 -1
  45. package/lib/module/useFocusEffect.js.map +1 -1
  46. package/lib/module/useKeyedChildListeners.js +4 -2
  47. package/lib/module/useKeyedChildListeners.js.map +1 -1
  48. package/lib/module/useNavigationBuilder.js +3 -2
  49. package/lib/module/useNavigationBuilder.js.map +1 -1
  50. package/lib/module/useNavigationHelpers.js +0 -4
  51. package/lib/module/useNavigationHelpers.js.map +1 -1
  52. package/lib/module/usePreventRemove.js +41 -0
  53. package/lib/module/usePreventRemove.js.map +1 -0
  54. package/lib/module/usePreventRemoveContext.js +12 -0
  55. package/lib/module/usePreventRemoveContext.js.map +1 -0
  56. package/lib/typescript/src/NavigationBuilderContext.d.ts +1 -1
  57. package/lib/typescript/src/PreventRemoveContext.d.ts +13 -0
  58. package/lib/typescript/src/PreventRemoveProvider.d.ts +9 -0
  59. package/lib/typescript/src/index.d.ts +4 -0
  60. package/lib/typescript/src/types.d.ts +10 -6
  61. package/lib/typescript/src/useComponent.d.ts +5 -1
  62. package/lib/typescript/src/useDescriptors.d.ts +3 -3
  63. package/lib/typescript/src/useNavigationBuilder.d.ts +9 -7
  64. package/lib/typescript/src/useNavigationHelpers.d.ts +3 -3
  65. package/lib/typescript/src/usePreventRemove.d.ts +12 -0
  66. package/lib/typescript/src/usePreventRemoveContext.d.ts +4 -0
  67. package/package.json +10 -9
  68. package/src/PreventRemoveContext.tsx +21 -0
  69. package/src/PreventRemoveProvider.tsx +126 -0
  70. package/src/getStateFromPath.tsx +4 -1
  71. package/src/index.tsx +4 -0
  72. package/src/types.tsx +39 -14
  73. package/src/useChildListeners.tsx +5 -3
  74. package/src/useComponent.tsx +19 -12
  75. package/src/useEventEmitter.tsx +14 -3
  76. package/src/useFocusEffect.tsx +1 -1
  77. package/src/useKeyedChildListeners.tsx +8 -4
  78. package/src/useNavigationBuilder.tsx +6 -3
  79. package/src/useNavigationHelpers.tsx +0 -6
  80. package/src/usePreventRemove.tsx +51 -0
  81. package/src/usePreventRemoveContext.tsx +15 -0
@@ -50,8 +50,8 @@ export default function useDescriptors<State extends NavigationState, ActionHelp
50
50
  source?: string | undefined;
51
51
  target?: string | undefined;
52
52
  }>)): void;
53
- navigate<RouteName extends string>(...args: [screen: RouteName] | [screen: RouteName, params: object | undefined]): void;
54
- navigate<RouteName_1 extends string>(options: {
53
+ navigate<RouteName extends string>(...args: RouteName extends unknown ? [screen: RouteName] | [screen: RouteName, params: object | undefined] : never): void;
54
+ navigate<RouteName_1 extends string>(options: RouteName_1 extends unknown ? {
55
55
  key: string;
56
56
  params?: object | undefined;
57
57
  merge?: boolean | undefined;
@@ -60,7 +60,7 @@ export default function useDescriptors<State extends NavigationState, ActionHelp
60
60
  key?: string | undefined;
61
61
  params: object | undefined;
62
62
  merge?: boolean | undefined;
63
- }): void;
63
+ } : never): void;
64
64
  reset(state: State | import("@react-navigation/routers").PartialState<State>): void;
65
65
  goBack(): void;
66
66
  isFocused(): boolean;
@@ -38,8 +38,8 @@ export default function useNavigationBuilder<State extends NavigationState, Rout
38
38
  source?: string | undefined;
39
39
  target?: string | undefined;
40
40
  }>)): void;
41
- navigate<RouteName extends string>(...args: [screen: RouteName] | [screen: RouteName, params: object | undefined]): void;
42
- navigate<RouteName_1 extends string>(options: {
41
+ navigate<RouteName extends string>(...args: RouteName extends unknown ? [screen: RouteName] | [screen: RouteName, params: object | undefined] : never): void;
42
+ navigate<RouteName_1 extends string>(options: RouteName_1 extends unknown ? {
43
43
  key: string;
44
44
  params?: object | undefined;
45
45
  merge?: boolean | undefined;
@@ -48,7 +48,7 @@ export default function useNavigationBuilder<State extends NavigationState, Rout
48
48
  key?: string | undefined;
49
49
  params: object | undefined;
50
50
  merge?: boolean | undefined;
51
- }): void;
51
+ } : never): void;
52
52
  reset(state: Readonly<{
53
53
  key: string;
54
54
  index: number;
@@ -119,8 +119,8 @@ export default function useNavigationBuilder<State extends NavigationState, Rout
119
119
  source?: string | undefined;
120
120
  target?: string | undefined;
121
121
  }>)): void;
122
- navigate<RouteName extends string>(...args: [screen: RouteName] | [screen: RouteName, params: object | undefined]): void;
123
- navigate<RouteName_1 extends string>(options: {
122
+ navigate<RouteName extends string>(...args: RouteName extends unknown ? [screen: RouteName] | [screen: RouteName, params: object | undefined] : never): void;
123
+ navigate<RouteName_1 extends string>(options: RouteName_1 extends unknown ? {
124
124
  key: string;
125
125
  params?: object | undefined;
126
126
  merge?: boolean | undefined;
@@ -129,7 +129,7 @@ export default function useNavigationBuilder<State extends NavigationState, Rout
129
129
  key?: string | undefined;
130
130
  params: object | undefined;
131
131
  merge?: boolean | undefined;
132
- }): void;
132
+ } : never): void;
133
133
  reset(state: State | PartialState<State>): void;
134
134
  goBack(): void;
135
135
  isFocused(): boolean;
@@ -158,5 +158,7 @@ export default function useNavigationBuilder<State extends NavigationState, Rout
158
158
  setParams(params: Partial<object | undefined>): void;
159
159
  setOptions(options: Partial<ScreenOptions>): void;
160
160
  } & import("./types").EventConsumer<EventMap & EventMapCore<State>> & PrivateValueStore<[ParamListBase, string, EventMap]> & ActionHelpers, import("./types").RouteProp<ParamListBase, string>>>;
161
- NavigationContent: (rest: Omit<React.ProviderProps<import("./types").NavigationHelpers<ParamListBase, {}> | undefined>, "value">) => JSX.Element;
161
+ NavigationContent: ({ children }: {
162
+ children: React.ReactNode;
163
+ }) => JSX.Element;
162
164
  };
@@ -40,8 +40,8 @@ export default function useNavigationHelpers<State extends NavigationState, Acti
40
40
  source?: string | undefined;
41
41
  target?: string | undefined;
42
42
  }>)): void;
43
- navigate<RouteName extends string>(...args: [screen: RouteName] | [screen: RouteName, params: object | undefined]): void;
44
- navigate<RouteName_1 extends string>(options: {
43
+ navigate<RouteName extends string>(...args: RouteName extends unknown ? [screen: RouteName] | [screen: RouteName, params: object | undefined] : never): void;
44
+ navigate<RouteName_1 extends string>(options: RouteName_1 extends unknown ? {
45
45
  key: string;
46
46
  params?: object | undefined;
47
47
  merge?: boolean | undefined;
@@ -50,7 +50,7 @@ export default function useNavigationHelpers<State extends NavigationState, Acti
50
50
  key?: string | undefined;
51
51
  params: object | undefined;
52
52
  merge?: boolean | undefined;
53
- }): void;
53
+ } : never): void;
54
54
  reset(state: Readonly<{
55
55
  key: string;
56
56
  index: number;
@@ -0,0 +1,12 @@
1
+ import type { NavigationAction } from '@react-navigation/routers';
2
+ /**
3
+ * Hook to prevent screen from being removed. Can be used to prevent users from leaving the screen.
4
+ *
5
+ * @param preventRemove Boolean indicating whether to prevent screen from being removed.
6
+ * @param callback Function which is executed when screen was prevented from being removed.
7
+ */
8
+ export default function usePreventRemove(preventRemove: boolean, callback: (options: {
9
+ data: {
10
+ action: NavigationAction;
11
+ };
12
+ }) => void): void;
@@ -0,0 +1,4 @@
1
+ export default function usePreventRemoveContext(): {
2
+ preventedRoutes: import("./PreventRemoveContext").PreventedRoutes;
3
+ setPreventRemove: (id: string, routeKey: string, preventRemove: boolean) => void;
4
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/core",
3
3
  "description": "Core utilities for building navigators",
4
- "version": "6.2.0",
4
+ "version": "6.3.0",
5
5
  "keywords": [
6
6
  "react",
7
7
  "react-native",
@@ -31,26 +31,27 @@
31
31
  "access": "public"
32
32
  },
33
33
  "scripts": {
34
- "prepare": "bob build",
34
+ "prepack": "bob build",
35
35
  "clean": "del lib"
36
36
  },
37
37
  "dependencies": {
38
- "@react-navigation/routers": "^6.1.0",
38
+ "@react-navigation/routers": "^6.1.2",
39
39
  "escape-string-regexp": "^4.0.0",
40
40
  "nanoid": "^3.1.23",
41
41
  "query-string": "^7.0.0",
42
- "react-is": "^16.13.0"
42
+ "react-is": "^16.13.0",
43
+ "use-latest-callback": "^0.1.5"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@testing-library/react-native": "^7.2.0",
46
- "@types/react": "^17.0.9",
47
+ "@types/react": "^17.0.47",
47
48
  "@types/react-is": "^17.0.0",
48
49
  "del-cli": "^3.0.1",
49
50
  "immer": "^9.0.2",
50
- "react": "17.0.1",
51
+ "react": "17.0.2",
51
52
  "react-native-builder-bob": "^0.18.1",
52
- "react-test-renderer": "17.0.1",
53
- "typescript": "^4.3.2"
53
+ "react-test-renderer": "17.0.2",
54
+ "typescript": "^4.7.4"
54
55
  },
55
56
  "peerDependencies": {
56
57
  "react": "*"
@@ -69,5 +70,5 @@
69
70
  ]
70
71
  ]
71
72
  },
72
- "gitHead": "c5ef6b5e88426e658123ea8590da583314b9001e"
73
+ "gitHead": "83aa392f1e13796d609c9df0afa44699ce4bacae"
73
74
  }
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * A type of an object that have a route key as an object key
5
+ * and a value whether to prevent that route.
6
+ */
7
+ export type PreventedRoutes = Record<string, { preventRemove: boolean }>;
8
+
9
+ const PreventRemoveContext = React.createContext<
10
+ | {
11
+ preventedRoutes: PreventedRoutes;
12
+ setPreventRemove: (
13
+ id: string,
14
+ routeKey: string,
15
+ preventRemove: boolean
16
+ ) => void;
17
+ }
18
+ | undefined
19
+ >(undefined);
20
+
21
+ export default PreventRemoveContext;
@@ -0,0 +1,126 @@
1
+ import { nanoid } from 'nanoid/non-secure';
2
+ import * as React from 'react';
3
+ import useLatestCallback from 'use-latest-callback';
4
+
5
+ import NavigationHelpersContext from './NavigationHelpersContext';
6
+ import NavigationRouteContext from './NavigationRouteContext';
7
+ import PreventRemoveContext, { PreventedRoutes } from './PreventRemoveContext';
8
+
9
+ type Props = {
10
+ children: React.ReactNode;
11
+ };
12
+
13
+ type PreventedRoutesMap = Map<
14
+ string,
15
+ {
16
+ routeKey: string;
17
+ preventRemove: boolean;
18
+ }
19
+ >;
20
+
21
+ /**
22
+ * Util function to transform map of prevented routes to a simpler object.
23
+ */
24
+ const transformPreventedRoutes = (
25
+ preventedRoutesMap: PreventedRoutesMap
26
+ ): PreventedRoutes => {
27
+ const preventedRoutesToTransform = [...preventedRoutesMap.values()];
28
+
29
+ const preventedRoutes = preventedRoutesToTransform.reduce<PreventedRoutes>(
30
+ (acc, { routeKey, preventRemove }) => {
31
+ acc[routeKey] = {
32
+ preventRemove: acc[routeKey]?.preventRemove || preventRemove,
33
+ };
34
+ return acc;
35
+ },
36
+ {}
37
+ );
38
+
39
+ return preventedRoutes;
40
+ };
41
+
42
+ /**
43
+ * Component used for managing which routes have to be prevented from removal in native-stack.
44
+ */
45
+ export default function PreventRemoveProvider({ children }: Props) {
46
+ const [parentId] = React.useState(() => nanoid());
47
+ const [preventedRoutesMap, setPreventedRoutesMap] =
48
+ React.useState<PreventedRoutesMap>(new Map());
49
+
50
+ const navigation = React.useContext(NavigationHelpersContext);
51
+ const route = React.useContext(NavigationRouteContext);
52
+
53
+ const preventRemoveContextValue = React.useContext(PreventRemoveContext);
54
+ // take `setPreventRemove` from parent context - if exist it means we're in a nested context
55
+ const setParentPrevented = preventRemoveContextValue?.setPreventRemove;
56
+
57
+ const setPreventRemove = useLatestCallback(
58
+ (id: string, routeKey: string, preventRemove: boolean): void => {
59
+ if (
60
+ preventRemove &&
61
+ (navigation == null ||
62
+ navigation
63
+ ?.getState()
64
+ .routes.every((route) => route.key !== routeKey))
65
+ ) {
66
+ throw new Error(
67
+ `Couldn't find a route with the key ${routeKey}. Is your component inside NavigationContent?`
68
+ );
69
+ }
70
+
71
+ setPreventedRoutesMap((prevPrevented) => {
72
+ // values haven't changed - do nothing
73
+ if (
74
+ routeKey === prevPrevented.get(id)?.routeKey &&
75
+ preventRemove === prevPrevented.get(id)?.preventRemove
76
+ ) {
77
+ return prevPrevented;
78
+ }
79
+
80
+ const nextPrevented = new Map(prevPrevented);
81
+
82
+ if (preventRemove) {
83
+ nextPrevented.set(id, {
84
+ routeKey,
85
+ preventRemove,
86
+ });
87
+ } else {
88
+ nextPrevented.delete(id);
89
+ }
90
+
91
+ return nextPrevented;
92
+ });
93
+ }
94
+ );
95
+
96
+ const isPrevented = [...preventedRoutesMap.values()].some(
97
+ ({ preventRemove }) => preventRemove
98
+ );
99
+
100
+ React.useEffect(() => {
101
+ if (route?.key !== undefined && setParentPrevented !== undefined) {
102
+ // when route is defined (and setParentPrevented) it means we're in a nested stack
103
+ // route.key then will be the route key of parent
104
+ setParentPrevented(parentId, route.key, isPrevented);
105
+ return () => {
106
+ setParentPrevented(parentId, route.key, false);
107
+ };
108
+ }
109
+
110
+ return;
111
+ }, [parentId, isPrevented, route?.key, setParentPrevented]);
112
+
113
+ const value = React.useMemo(
114
+ () => ({
115
+ setPreventRemove,
116
+ preventedRoutes: transformPreventedRoutes(preventedRoutesMap),
117
+ }),
118
+ [setPreventRemove, preventedRoutesMap]
119
+ );
120
+
121
+ return (
122
+ <PreventRemoveContext.Provider value={value}>
123
+ {children}
124
+ </PreventRemoveContext.Provider>
125
+ );
126
+ }
@@ -565,7 +565,10 @@ const parseQueryParams = (
565
565
 
566
566
  if (parseConfig) {
567
567
  Object.keys(params).forEach((name) => {
568
- if (parseConfig[name] && typeof params[name] === 'string') {
568
+ if (
569
+ Object.hasOwnProperty.call(parseConfig, name) &&
570
+ typeof params[name] === 'string'
571
+ ) {
569
572
  params[name] = parseConfig[name](params[name] as string);
570
573
  }
571
574
  });
package/src/index.tsx CHANGED
@@ -11,6 +11,8 @@ export { default as NavigationContainerRefContext } from './NavigationContainerR
11
11
  export { default as NavigationContext } from './NavigationContext';
12
12
  export { default as NavigationHelpersContext } from './NavigationHelpersContext';
13
13
  export { default as NavigationRouteContext } from './NavigationRouteContext';
14
+ export { default as PreventRemoveContext } from './PreventRemoveContext';
15
+ export { default as PreventRemoveProvider } from './PreventRemoveProvider';
14
16
  export * from './types';
15
17
  export { default as useFocusEffect } from './useFocusEffect';
16
18
  export { default as useIsFocused } from './useIsFocused';
@@ -18,6 +20,8 @@ export { default as useNavigation } from './useNavigation';
18
20
  export { default as useNavigationBuilder } from './useNavigationBuilder';
19
21
  export { default as useNavigationContainerRef } from './useNavigationContainerRef';
20
22
  export { default as useNavigationState } from './useNavigationState';
23
+ export { default as UNSTABLE_usePreventRemove } from './usePreventRemove';
24
+ export { default as usePreventRemoveContext } from './usePreventRemoveContext';
21
25
  export { default as useRoute } from './useRoute';
22
26
  export { default as validatePathConfig } from './validatePathConfig';
23
27
  export * from '@react-navigation/routers';
package/src/types.tsx CHANGED
@@ -192,9 +192,18 @@ type NavigationHelpersCommon<
192
192
  * @param [params] Params object for the route.
193
193
  */
194
194
  navigate<RouteName extends keyof ParamList>(
195
- ...args: undefined extends ParamList[RouteName]
196
- ? [screen: RouteName] | [screen: RouteName, params: ParamList[RouteName]]
197
- : [screen: RouteName, params: ParamList[RouteName]]
195
+ ...args: // this first condition allows us to iterate over a union type
196
+ // This is to avoid getting a union of all the params from `ParamList[RouteName]`,
197
+ // which will get our types all mixed up if a union RouteName is passed in.
198
+ RouteName extends unknown
199
+ ? // This condition checks if the params are optional,
200
+ // which means it's either undefined or a union with undefined
201
+ undefined extends ParamList[RouteName]
202
+ ?
203
+ | [screen: RouteName] // if the params are optional, we don't have to provide it
204
+ | [screen: RouteName, params: ParamList[RouteName]]
205
+ : [screen: RouteName, params: ParamList[RouteName]]
206
+ : never
198
207
  ): void;
199
208
 
200
209
  /**
@@ -203,14 +212,16 @@ type NavigationHelpersCommon<
203
212
  * @param route Object with `key` or `name` for the route to navigate to, and a `params` object.
204
213
  */
205
214
  navigate<RouteName extends keyof ParamList>(
206
- options:
207
- | { key: string; params?: ParamList[RouteName]; merge?: boolean }
208
- | {
209
- name: RouteName;
210
- key?: string;
211
- params: ParamList[RouteName];
212
- merge?: boolean;
213
- }
215
+ options: RouteName extends unknown
216
+ ?
217
+ | { key: string; params?: ParamList[RouteName]; merge?: boolean }
218
+ | {
219
+ name: RouteName;
220
+ key?: string;
221
+ params: ParamList[RouteName];
222
+ merge?: boolean;
223
+ }
224
+ : never
214
225
  ): void;
215
226
 
216
227
  /**
@@ -325,7 +336,11 @@ export type NavigationProp<
325
336
  *
326
337
  * @param params Params object for the current route.
327
338
  */
328
- setParams(params: Partial<ParamList[RouteName]>): void;
339
+ setParams(
340
+ params: ParamList[RouteName] extends undefined
341
+ ? undefined
342
+ : Partial<ParamList[RouteName]>
343
+ ): void;
329
344
 
330
345
  /**
331
346
  * Update the options for the route.
@@ -436,6 +451,16 @@ export type ScreenListeners<
436
451
  >;
437
452
  }>;
438
453
 
454
+ type ScreenComponentType<
455
+ ParamList extends ParamListBase,
456
+ RouteName extends keyof ParamList
457
+ > =
458
+ | React.ComponentType<{
459
+ route: RouteProp<ParamList, RouteName>;
460
+ navigation: any;
461
+ }>
462
+ | React.ComponentType<{}>;
463
+
439
464
  export type RouteConfigComponent<
440
465
  ParamList extends ParamListBase,
441
466
  RouteName extends keyof ParamList
@@ -444,7 +469,7 @@ export type RouteConfigComponent<
444
469
  /**
445
470
  * React component to render for this screen.
446
471
  */
447
- component: React.ComponentType<any>;
472
+ component: ScreenComponentType<ParamList, RouteName>;
448
473
  getComponent?: never;
449
474
  children?: never;
450
475
  }
@@ -452,7 +477,7 @@ export type RouteConfigComponent<
452
477
  /**
453
478
  * Lazily get a React component to render for this screen.
454
479
  */
455
- getComponent: () => React.ComponentType<any>;
480
+ getComponent: () => ScreenComponentType<ParamList, RouteName>;
456
481
  component?: never;
457
482
  children?: never;
458
483
  }
@@ -15,14 +15,16 @@ export default function useChildListeners() {
15
15
 
16
16
  const addListener = React.useCallback(
17
17
  <T extends keyof ListenerMap>(type: T, listener: ListenerMap[T]) => {
18
- // @ts-expect-error: listener should be correct type according to `type`
19
18
  listeners[type].push(listener);
20
19
 
20
+ let removed = false;
21
21
  return () => {
22
- // @ts-expect-error: listener should be correct type according to `type`
23
22
  const index = listeners[type].indexOf(listener);
24
23
 
25
- listeners[type].splice(index, 1);
24
+ if (!removed && index > -1) {
25
+ removed = true;
26
+ listeners[type].splice(index, 1);
27
+ }
26
28
  };
27
29
  },
28
30
  [listeners]
@@ -1,30 +1,37 @@
1
1
  import * as React from 'react';
2
2
 
3
- export default function useComponent<
4
- T extends React.ComponentType<any>,
5
- P extends {}
6
- >(Component: T, props: P) {
7
- const propsRef = React.useRef<P | null>(props);
3
+ type Render = (children: React.ReactNode) => JSX.Element;
4
+
5
+ type Props = {
6
+ render: Render;
7
+ children: React.ReactNode;
8
+ };
9
+
10
+ const NavigationContent = ({ render, children }: Props) => {
11
+ return render(children);
12
+ };
13
+
14
+ export default function useComponent(render: Render) {
15
+ const renderRef = React.useRef<Render | null>(render);
8
16
 
9
17
  // Normally refs shouldn't be mutated in render
10
18
  // But we return a component which will be rendered
11
19
  // So it's just for immediate consumption
12
- propsRef.current = props;
20
+ renderRef.current = render;
13
21
 
14
22
  React.useEffect(() => {
15
- propsRef.current = null;
23
+ renderRef.current = null;
16
24
  });
17
25
 
18
- return React.useRef((rest: Omit<React.ComponentProps<T>, keyof P>) => {
19
- const props = propsRef.current;
26
+ return React.useRef(({ children }: { children: React.ReactNode }) => {
27
+ const render = renderRef.current;
20
28
 
21
- if (props === null) {
29
+ if (render === null) {
22
30
  throw new Error(
23
31
  'The returned component must be rendered in the same render phase as the hook.'
24
32
  );
25
33
  }
26
34
 
27
- // @ts-expect-error: the props should be fine here
28
- return <Component {...props} {...rest} />;
35
+ return <NavigationContent render={render}>{children}</NavigationContent>;
29
36
  }).current;
30
37
  }
@@ -21,7 +21,9 @@ export default function useEventEmitter<T extends Record<string, any>>(
21
21
  listenRef.current = listen;
22
22
  });
23
23
 
24
- const listeners = React.useRef<Record<string, Record<string, Listeners>>>({});
24
+ const listeners = React.useRef<Record<string, Record<string, Listeners>>>(
25
+ Object.create(null)
26
+ );
25
27
 
26
28
  const create = React.useCallback((target: string) => {
27
29
  const removeListener = (type: string, callback: (data: any) => void) => {
@@ -35,7 +37,9 @@ export default function useEventEmitter<T extends Record<string, any>>(
35
37
 
36
38
  const index = callbacks.indexOf(callback);
37
39
 
38
- callbacks.splice(index, 1);
40
+ if (index > -1) {
41
+ callbacks.splice(index, 1);
42
+ }
39
43
  };
40
44
 
41
45
  const addListener = (type: string, callback: (data: any) => void) => {
@@ -43,7 +47,14 @@ export default function useEventEmitter<T extends Record<string, any>>(
43
47
  listeners.current[type][target] = listeners.current[type][target] || [];
44
48
  listeners.current[type][target].push(callback);
45
49
 
46
- return () => removeListener(type, callback);
50
+ let removed = false;
51
+ return () => {
52
+ // Prevent removing other listeners when unsubscribing same listener multiple times
53
+ if (!removed) {
54
+ removed = true;
55
+ removeListener(type, callback);
56
+ }
57
+ };
47
58
  };
48
59
 
49
60
  return {
@@ -52,7 +52,7 @@ export default function useFocusEffect(effect: EffectCallback) {
52
52
  'Instead, write the async function inside your effect ' +
53
53
  'and call it immediately:\n\n' +
54
54
  'useFocusEffect(\n' +
55
- ' React.useCallback() => {\n' +
55
+ ' React.useCallback(() => {\n' +
56
56
  ' async function fetchData() {\n' +
57
57
  ' // You can await here\n' +
58
58
  ' const response = await MyAPI.getData(someId);\n' +
@@ -11,10 +11,12 @@ export default function useKeyedChildListeners() {
11
11
  string,
12
12
  KeyedListenerMap[K] | undefined
13
13
  >;
14
- }>({
15
- getState: {},
16
- beforeRemove: {},
17
- });
14
+ }>(
15
+ Object.assign(Object.create(null), {
16
+ getState: {},
17
+ beforeRemove: {},
18
+ })
19
+ );
18
20
 
19
21
  const addKeyedListener = React.useCallback(
20
22
  <T extends keyof KeyedListenerMap>(
@@ -22,9 +24,11 @@ export default function useKeyedChildListeners() {
22
24
  key: string,
23
25
  listener: KeyedListenerMap[T]
24
26
  ) => {
27
+ // @ts-expect-error: according to ref stated above you can use `key` to index type
25
28
  keyedListeners[type][key] = listener;
26
29
 
27
30
  return () => {
31
+ // @ts-expect-error: according to ref stated above you can use `key` to index type
28
32
  keyedListeners[type][key] = undefined;
29
33
  };
30
34
  },
@@ -19,6 +19,7 @@ import isRecordEqual from './isRecordEqual';
19
19
  import NavigationHelpersContext from './NavigationHelpersContext';
20
20
  import NavigationRouteContext from './NavigationRouteContext';
21
21
  import NavigationStateContext from './NavigationStateContext';
22
+ import PreventRemoveProvider from './PreventRemoveProvider';
22
23
  import Screen from './Screen';
23
24
  import {
24
25
  DefaultNavigatorOptions,
@@ -690,9 +691,11 @@ export default function useNavigationBuilder<
690
691
  descriptors,
691
692
  });
692
693
 
693
- const NavigationContent = useComponent(NavigationHelpersContext.Provider, {
694
- value: navigation,
695
- });
694
+ const NavigationContent = useComponent((children: React.ReactNode) => (
695
+ <NavigationHelpersContext.Provider value={navigation}>
696
+ <PreventRemoveProvider>{children}</PreventRemoveProvider>
697
+ </NavigationHelpersContext.Provider>
698
+ ));
696
699
 
697
700
  return {
698
701
  state,
@@ -95,12 +95,6 @@ export default function useNavigationHelpers<
95
95
  current = current.getParent();
96
96
  }
97
97
 
98
- if (current == null) {
99
- throw new Error(
100
- `Couldn't find a parent navigator with the ID "${id}". Is this navigator nested under another navigator with this ID?`
101
- );
102
- }
103
-
104
98
  return current;
105
99
  }
106
100