UNPKG

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