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