@react-navigation/core 7.0.0-alpha.11 → 7.0.0-alpha.13

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 (41) hide show
  1. package/lib/commonjs/StaticNavigation.js +32 -22
  2. package/lib/commonjs/StaticNavigation.js.map +1 -1
  3. package/lib/commonjs/deepFreeze.js +37 -0
  4. package/lib/commonjs/deepFreeze.js.map +1 -0
  5. package/lib/commonjs/useFocusEffect.js +2 -2
  6. package/lib/commonjs/useNavigationBuilder.js +3 -2
  7. package/lib/commonjs/useNavigationBuilder.js.map +1 -1
  8. package/lib/commonjs/useRouteCache.js +15 -1
  9. package/lib/commonjs/useRouteCache.js.map +1 -1
  10. package/lib/commonjs/useSyncState.js +12 -3
  11. package/lib/commonjs/useSyncState.js.map +1 -1
  12. package/lib/module/StaticNavigation.js +32 -22
  13. package/lib/module/StaticNavigation.js.map +1 -1
  14. package/lib/module/deepFreeze.js +29 -0
  15. package/lib/module/deepFreeze.js.map +1 -0
  16. package/lib/module/useFocusEffect.js +2 -2
  17. package/lib/module/useNavigationBuilder.js +3 -2
  18. package/lib/module/useNavigationBuilder.js.map +1 -1
  19. package/lib/module/useRouteCache.js +15 -1
  20. package/lib/module/useRouteCache.js.map +1 -1
  21. package/lib/module/useSyncState.js +12 -3
  22. package/lib/module/useSyncState.js.map +1 -1
  23. package/lib/typescript/src/StaticNavigation.d.ts +20 -4
  24. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  25. package/lib/typescript/src/deepFreeze.d.ts +3 -0
  26. package/lib/typescript/src/deepFreeze.d.ts.map +1 -0
  27. package/lib/typescript/src/types.d.ts +1 -1
  28. package/lib/typescript/src/types.d.ts.map +1 -1
  29. package/lib/typescript/src/useNavigationBuilder.d.ts +0 -21
  30. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  31. package/lib/typescript/src/useRouteCache.d.ts +1 -1
  32. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  33. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  34. package/package.json +2 -2
  35. package/src/StaticNavigation.tsx +95 -44
  36. package/src/deepFreeze.tsx +34 -0
  37. package/src/types.tsx +1 -1
  38. package/src/useFocusEffect.tsx +2 -2
  39. package/src/useNavigationBuilder.tsx +7 -4
  40. package/src/useRouteCache.tsx +17 -1
  41. package/src/useSyncState.tsx +16 -3
@@ -0,0 +1,34 @@
1
+ export const isPlainObject = (value: unknown): value is object => {
2
+ if (typeof value === 'object' && value !== null) {
3
+ return Object.getPrototypeOf(value) === Object.prototype;
4
+ }
5
+
6
+ return false;
7
+ };
8
+
9
+ export const deepFreeze = <T,>(object: T): Readonly<T> => {
10
+ // We only freeze in development to catch issues early
11
+ // Don't freeze in production to avoid unnecessary performance overhead
12
+ if (process.env.NODE_ENV === 'production') {
13
+ return object;
14
+ }
15
+
16
+ if (Object.isFrozen(object)) {
17
+ return object;
18
+ }
19
+
20
+ if (!isPlainObject(object) && !Array.isArray(object)) {
21
+ return object;
22
+ }
23
+
24
+ // Freeze properties before freezing self
25
+ for (const key in object) {
26
+ if (Object.getOwnPropertyDescriptor(object, key)?.configurable) {
27
+ const value = object[key];
28
+
29
+ deepFreeze(value);
30
+ }
31
+ }
32
+
33
+ return Object.freeze(object);
34
+ };
package/src/types.tsx CHANGED
@@ -555,7 +555,7 @@ export type ScreenListeners<
555
555
  EventMap extends EventMapBase,
556
556
  > = Partial<{
557
557
  [EventName in keyof (EventMap & EventMapCore<State>)]: EventListenerCallback<
558
- EventMap,
558
+ EventMap & EventMapCore<State>,
559
559
  EventName
560
560
  >;
561
561
  }>;
@@ -71,7 +71,7 @@ export function useFocusEffect(effect: EffectCallback) {
71
71
  }
72
72
  };
73
73
 
74
- // We need to run the effect on intial render/dep changes if the screen is focused
74
+ // We need to run the effect on initial render/dep changes if the screen is focused
75
75
  if (navigation.isFocused()) {
76
76
  cleanup = callback();
77
77
  isFocused = true;
@@ -79,7 +79,7 @@ export function useFocusEffect(effect: EffectCallback) {
79
79
 
80
80
  const unsubscribeFocus = navigation.addListener('focus', () => {
81
81
  // If callback was already called for focus, avoid calling it again
82
- // The focus event may also fire on intial render, so we guard against runing the effect twice
82
+ // The focus event may also fire on initial render, so we guard against running the effect twice
83
83
  if (isFocused) {
84
84
  return;
85
85
  }
@@ -14,6 +14,7 @@ import * as React from 'react';
14
14
  import { isValidElementType } from 'react-is';
15
15
  import useLatestCallback from 'use-latest-callback';
16
16
 
17
+ import { deepFreeze } from './deepFreeze';
17
18
  import { Group } from './Group';
18
19
  import { isArrayEqual } from './isArrayEqual';
19
20
  import { isRecordEqual } from './isRecordEqual';
@@ -457,7 +458,7 @@ export function useNavigationBuilder<
457
458
 
458
459
  let state =
459
460
  // If the state isn't initialized, or stale, use the state we initialized instead
460
- // The state won't update until there's a change needed in the state we have initalized locally
461
+ // The state won't update until there's a change needed in the state we have initialized locally
461
462
  // So it'll be `undefined` or stale until the first navigation event happens
462
463
  isStateInitialized(currentState)
463
464
  ? (currentState as State)
@@ -582,9 +583,11 @@ export function useNavigationBuilder<
582
583
  const getState = useLatestCallback((): State => {
583
584
  const currentState = shouldUpdate ? nextState : getCurrentState();
584
585
 
585
- return (
586
- isStateInitialized(currentState) ? currentState : initializedState
587
- ) as State;
586
+ return deepFreeze(
587
+ (isStateInitialized(currentState)
588
+ ? currentState
589
+ : initializedState) as State
590
+ );
588
591
  });
589
592
 
590
593
  const emitter = useEventEmitter<EventMapCore<State>>((e) => {
@@ -7,7 +7,7 @@ import type { RouteProp } from './types';
7
7
  type RouteCache = Map<string, RouteProp<ParamListBase>>;
8
8
 
9
9
  /**
10
- * Utilites such as `getFocusedRouteNameFromRoute` need to access state.
10
+ * Utilities such as `getFocusedRouteNameFromRoute` need to access state.
11
11
  * So we need a way to suppress the warning for those use cases.
12
12
  * This is fine since they are internal utilities and this is not public API.
13
13
  */
@@ -41,6 +41,22 @@ export function useRouteCache<State extends NavigationState>(
41
41
  proxy = routeWithoutState;
42
42
  }
43
43
 
44
+ if (process.env.NODE_ENV !== 'production') {
45
+ // FIXME: since the state is updated with mutation, the route object cannot be frozen
46
+ // As a workaround, loop through the object and make the properties readonly
47
+ for (const key in proxy) {
48
+ // @ts-expect-error: this is fine since we are looping through the object
49
+ const value = proxy[key];
50
+
51
+ Object.defineProperty(proxy, key, {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: false,
55
+ value,
56
+ });
57
+ }
58
+ }
59
+
44
60
  Object.defineProperty(proxy, CHILD_STATE, {
45
61
  enumerable: false,
46
62
  configurable: true,
@@ -1,14 +1,27 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import { deepFreeze } from './deepFreeze';
4
+
3
5
  const createStore = <T,>(getInitialState: () => T) => {
4
6
  const listeners: (() => void)[] = [];
5
7
 
6
- let state: T = getInitialState();
8
+ let initialized = false;
9
+ let state: T;
10
+
11
+ const getState = () => {
12
+ if (initialized) {
13
+ return state;
14
+ }
7
15
 
8
- const getState = () => state;
16
+ initialized = true;
17
+ state = deepFreeze(getInitialState());
18
+
19
+ return state;
20
+ };
9
21
 
10
22
  const setState = (newState: T) => {
11
- state = newState;
23
+ state = deepFreeze(newState);
24
+
12
25
  listeners.forEach((listener) => listener());
13
26
  };
14
27