1// Forked from React Navigation in order to use a custom `useLinking` -> `extractPathFromURL` function.
2// https://github.com/react-navigation/react-navigation/blob/main/packages/native/src/NavigationContainer.tsx
3import {
4  BaseNavigationContainer,
5  getActionFromState,
6  getPathFromState,
7  getStateFromPath,
8  NavigationContainerProps,
9  NavigationContainerRef,
10  ParamListBase,
11  validatePathConfig,
12} from '@react-navigation/core';
13import {
14  DefaultTheme,
15  DocumentTitleOptions,
16  LinkingContext,
17  LinkingOptions,
18  Theme,
19  ThemeProvider,
20} from '@react-navigation/native';
21import useBackButton from '@react-navigation/native/src/useBackButton';
22import useDocumentTitle from '@react-navigation/native/src/useDocumentTitle';
23import useThenable from '@react-navigation/native/src/useThenable';
24import * as React from 'react';
25
26import useLinking from './useLinking';
27
28global.REACT_NAVIGATION_DEVTOOLS = new WeakMap();
29
30type Props<ParamList extends object> = NavigationContainerProps & {
31  theme?: Theme;
32  linking?: LinkingOptions<ParamList>;
33  fallback?: React.ReactNode;
34  documentTitle?: DocumentTitleOptions;
35  onReady?: () => void;
36};
37
38/**
39 * Container component which holds the navigation state designed for React Native apps.
40 * This should be rendered at the root wrapping the whole app.
41 *
42 * @param props.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will override deep links when specified. Make sure that you don't specify an `initialState` when there's a deep link (`Linking.getInitialURL()`).
43 * @param props.onReady Callback which is called after the navigation tree mounts.
44 * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
45 * @param props.theme Theme object for the navigators.
46 * @param props.linking Options for deep linking. Deep link handling is enabled when this prop is provided, unless `linking.enabled` is `false`.
47 * @param props.fallback Fallback component to render until we have finished getting initial state when linking is enabled. Defaults to `null`.
48 * @param props.documentTitle Options to configure the document title on Web. Updating document title is handled by default unless `documentTitle.enabled` is `false`.
49 * @param props.children Child elements to render the content.
50 * @param props.ref Ref object which refers to the navigation object containing helper methods.
51 */
52function NavigationContainerInner(
53  {
54    theme = DefaultTheme,
55    linking,
56    fallback = null,
57    documentTitle,
58    onReady,
59    ...rest
60  }: Props<ParamListBase>,
61  ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
62) {
63  const isLinkingEnabled = linking ? linking.enabled !== false : false;
64
65  if (linking?.config) {
66    validatePathConfig(linking.config);
67  }
68
69  const refContainer = React.useRef<NavigationContainerRef<ParamListBase>>(null);
70
71  useBackButton(refContainer);
72  useDocumentTitle(refContainer, documentTitle);
73
74  const { getInitialState } = useLinking(refContainer, {
75    // independent: rest.independent,
76    enabled: isLinkingEnabled,
77    prefixes: [],
78    ...linking,
79  });
80
81  // Add additional linking related info to the ref
82  // This will be used by the devtools
83  React.useEffect(() => {
84    if (refContainer.current) {
85      REACT_NAVIGATION_DEVTOOLS.set(refContainer.current, {
86        get linking() {
87          return {
88            ...linking,
89            enabled: isLinkingEnabled,
90            prefixes: linking?.prefixes ?? [],
91            getStateFromPath: linking?.getStateFromPath ?? getStateFromPath,
92            getPathFromState: linking?.getPathFromState ?? getPathFromState,
93            getActionFromState: linking?.getActionFromState ?? getActionFromState,
94          };
95        },
96      });
97    }
98  });
99
100  const [isResolved, initialState] = useThenable(getInitialState);
101
102  React.useImperativeHandle(ref, () => refContainer.current);
103
104  const linkingContext = React.useMemo(() => ({ options: linking }), [linking]);
105
106  const isReady = rest.initialState != null || !isLinkingEnabled || isResolved;
107
108  const onReadyRef = React.useRef(onReady);
109
110  React.useEffect(() => {
111    onReadyRef.current = onReady;
112  });
113
114  React.useEffect(() => {
115    if (isReady) {
116      onReadyRef.current?.();
117    }
118  }, [isReady]);
119
120  if (!isReady) {
121    // This is temporary until we have Suspense for data-fetching
122    // Then the fallback will be handled by a parent `Suspense` component
123    return fallback as React.ReactElement;
124  }
125
126  return (
127    <LinkingContext.Provider value={linkingContext}>
128      <ThemeProvider value={theme}>
129        <BaseNavigationContainer
130          {...rest}
131          initialState={rest.initialState == null ? initialState : rest.initialState}
132          ref={refContainer}
133        />
134      </ThemeProvider>
135    </LinkingContext.Provider>
136  );
137}
138
139const NavigationContainer = React.forwardRef(NavigationContainerInner) as <
140  RootParamList extends object = ReactNavigation.RootParamList,
141>(
142  props: Props<RootParamList> & {
143    ref?: React.Ref<NavigationContainerRef<RootParamList>>;
144  }
145) => React.ReactElement;
146
147export default NavigationContainer;
148