UNPKG

13.3 kBJavaScriptView Raw
1import { CommonActions } from '@react-navigation/routers';
2import * as React from 'react';
3import checkDuplicateRouteNames from './checkDuplicateRouteNames';
4import checkSerializable from './checkSerializable';
5import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
6import EnsureSingleNavigator from './EnsureSingleNavigator';
7import findFocusedRoute from './findFocusedRoute';
8import NavigationBuilderContext from './NavigationBuilderContext';
9import NavigationContainerRefContext from './NavigationContainerRefContext';
10import NavigationContext from './NavigationContext';
11import NavigationRouteContext from './NavigationRouteContext';
12import NavigationStateContext from './NavigationStateContext';
13import UnhandledActionContext from './UnhandledActionContext';
14import useChildListeners from './useChildListeners';
15import useEventEmitter from './useEventEmitter';
16import useKeyedChildListeners from './useKeyedChildListeners';
17import useOptionsGetters from './useOptionsGetters';
18import { ScheduleUpdateContext } from './useScheduleUpdate';
19import useSyncState from './useSyncState';
20const serializableWarnings = [];
21const duplicateNameWarnings = [];
22/**
23 * Remove `key` and `routeNames` from the state objects recursively to get partial state.
24 *
25 * @param state Initial state object.
26 */
27
28const getPartialState = state => {
29 if (state === undefined) {
30 return;
31 } // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
33
34 const {
35 key,
36 routeNames,
37 ...partialState
38 } = state;
39 return { ...partialState,
40 stale: true,
41 routes: state.routes.map(route => {
42 if (route.state === undefined) {
43 return route;
44 }
45
46 return { ...route,
47 state: getPartialState(route.state)
48 };
49 })
50 };
51};
52/**
53 * Container component which holds the navigation state.
54 * This should be rendered at the root wrapping the whole app.
55 *
56 * @param props.initialState Initial state object for the navigation tree.
57 * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
58 * @param props.children Child elements to render the content.
59 * @param props.ref Ref object which refers to the navigation object containing helper methods.
60 */
61
62
63const BaseNavigationContainer = /*#__PURE__*/React.forwardRef(function BaseNavigationContainer(_ref, ref) {
64 let {
65 initialState,
66 onStateChange,
67 onUnhandledAction,
68 independent,
69 children
70 } = _ref;
71 const parent = React.useContext(NavigationStateContext);
72
73 if (!parent.isDefault && !independent) {
74 throw new Error("Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them.");
75 }
76
77 const [state, getState, setState, scheduleUpdate, flushUpdates] = useSyncState(() => getPartialState(initialState == null ? undefined : initialState));
78 const isFirstMountRef = React.useRef(true);
79 const navigatorKeyRef = React.useRef();
80 const getKey = React.useCallback(() => navigatorKeyRef.current, []);
81 const setKey = React.useCallback(key => {
82 navigatorKeyRef.current = key;
83 }, []);
84 const {
85 listeners,
86 addListener
87 } = useChildListeners();
88 const {
89 keyedListeners,
90 addKeyedListener
91 } = useKeyedChildListeners();
92 const dispatch = React.useCallback(action => {
93 if (listeners.focus[0] == null) {
94 console.error(NOT_INITIALIZED_ERROR);
95 } else {
96 listeners.focus[0](navigation => navigation.dispatch(action));
97 }
98 }, [listeners.focus]);
99 const canGoBack = React.useCallback(() => {
100 if (listeners.focus[0] == null) {
101 return false;
102 }
103
104 const {
105 result,
106 handled
107 } = listeners.focus[0](navigation => navigation.canGoBack());
108
109 if (handled) {
110 return result;
111 } else {
112 return false;
113 }
114 }, [listeners.focus]);
115 const resetRoot = React.useCallback(state => {
116 var _state$key, _keyedListeners$getSt, _keyedListeners$getSt2;
117
118 const target = (_state$key = state === null || state === void 0 ? void 0 : state.key) !== null && _state$key !== void 0 ? _state$key : (_keyedListeners$getSt = (_keyedListeners$getSt2 = keyedListeners.getState).root) === null || _keyedListeners$getSt === void 0 ? void 0 : _keyedListeners$getSt.call(_keyedListeners$getSt2).key;
119
120 if (target == null) {
121 console.error(NOT_INITIALIZED_ERROR);
122 } else {
123 listeners.focus[0](navigation => navigation.dispatch({ ...CommonActions.reset(state),
124 target
125 }));
126 }
127 }, [keyedListeners.getState, listeners.focus]);
128 const getRootState = React.useCallback(() => {
129 var _keyedListeners$getSt3, _keyedListeners$getSt4;
130
131 return (_keyedListeners$getSt3 = (_keyedListeners$getSt4 = keyedListeners.getState).root) === null || _keyedListeners$getSt3 === void 0 ? void 0 : _keyedListeners$getSt3.call(_keyedListeners$getSt4);
132 }, [keyedListeners.getState]);
133 const getCurrentRoute = React.useCallback(() => {
134 const state = getRootState();
135
136 if (state == null) {
137 return undefined;
138 }
139
140 const route = findFocusedRoute(state);
141 return route;
142 }, [getRootState]);
143 const emitter = useEventEmitter();
144 const {
145 addOptionsGetter,
146 getCurrentOptions
147 } = useOptionsGetters({});
148 const navigation = React.useMemo(() => ({ ...Object.keys(CommonActions).reduce((acc, name) => {
149 acc[name] = function () {
150 return (// @ts-expect-error: this is ok
151 dispatch(CommonActions[name](...arguments))
152 );
153 };
154
155 return acc;
156 }, {}),
157 ...emitter.create('root'),
158 dispatch,
159 resetRoot,
160 isFocused: () => true,
161 canGoBack,
162 getParent: () => undefined,
163 getState: () => stateRef.current,
164 getRootState,
165 getCurrentRoute,
166 getCurrentOptions,
167 isReady: () => listeners.focus[0] != null
168 }), [canGoBack, dispatch, emitter, getCurrentOptions, getCurrentRoute, getRootState, listeners.focus, resetRoot]);
169 React.useImperativeHandle(ref, () => navigation, [navigation]);
170 const onDispatchAction = React.useCallback((action, noop) => {
171 emitter.emit({
172 type: '__unsafe_action__',
173 data: {
174 action,
175 noop,
176 stack: stackRef.current
177 }
178 });
179 }, [emitter]);
180 const lastEmittedOptionsRef = React.useRef();
181 const onOptionsChange = React.useCallback(options => {
182 if (lastEmittedOptionsRef.current === options) {
183 return;
184 }
185
186 lastEmittedOptionsRef.current = options;
187 emitter.emit({
188 type: 'options',
189 data: {
190 options
191 }
192 });
193 }, [emitter]);
194 const stackRef = React.useRef();
195 const builderContext = React.useMemo(() => ({
196 addListener,
197 addKeyedListener,
198 onDispatchAction,
199 onOptionsChange,
200 stackRef
201 }), [addListener, addKeyedListener, onDispatchAction, onOptionsChange]);
202 const scheduleContext = React.useMemo(() => ({
203 scheduleUpdate,
204 flushUpdates
205 }), [scheduleUpdate, flushUpdates]);
206 const isInitialRef = React.useRef(true);
207 const getIsInitial = React.useCallback(() => isInitialRef.current, []);
208 const context = React.useMemo(() => ({
209 state,
210 getState,
211 setState,
212 getKey,
213 setKey,
214 getIsInitial,
215 addOptionsGetter
216 }), [state, getState, setState, getKey, setKey, getIsInitial, addOptionsGetter]);
217 const onStateChangeRef = React.useRef(onStateChange);
218 const stateRef = React.useRef(state);
219 React.useEffect(() => {
220 isInitialRef.current = false;
221 onStateChangeRef.current = onStateChange;
222 stateRef.current = state;
223 });
224 React.useEffect(() => {
225 const hydratedState = getRootState();
226
227 if (process.env.NODE_ENV !== 'production') {
228 if (hydratedState !== undefined) {
229 const serializableResult = checkSerializable(hydratedState);
230
231 if (!serializableResult.serializable) {
232 const {
233 location,
234 reason
235 } = serializableResult;
236 let path = '';
237 let pointer = hydratedState;
238 let params = false;
239
240 for (let i = 0; i < location.length; i++) {
241 const curr = location[i];
242 const prev = location[i - 1];
243 pointer = pointer[curr];
244
245 if (!params && curr === 'state') {
246 continue;
247 } else if (!params && curr === 'routes') {
248 if (path) {
249 path += ' > ';
250 }
251 } else if (!params && typeof curr === 'number' && prev === 'routes') {
252 var _pointer;
253
254 path += (_pointer = pointer) === null || _pointer === void 0 ? void 0 : _pointer.name;
255 } else if (!params) {
256 path += ` > ${curr}`;
257 params = true;
258 } else {
259 if (typeof curr === 'number' || /^[0-9]+$/.test(curr)) {
260 path += `[${curr}]`;
261 } else if (/^[a-z$_]+$/i.test(curr)) {
262 path += `.${curr}`;
263 } else {
264 path += `[${JSON.stringify(curr)}]`;
265 }
266 }
267 }
268
269 const message = `Non-serializable values were found in the navigation state. Check:\n\n${path} (${reason})\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.`;
270
271 if (!serializableWarnings.includes(message)) {
272 serializableWarnings.push(message);
273 console.warn(message);
274 }
275 }
276
277 const duplicateRouteNamesResult = checkDuplicateRouteNames(hydratedState);
278
279 if (duplicateRouteNamesResult.length) {
280 const message = `Found screens with the same name nested inside one another. Check:\n${duplicateRouteNamesResult.map(locations => `\n${locations.join(', ')}`)}\n\nThis can cause confusing behavior during navigation. Consider using unique names for each screen instead.`;
281
282 if (!duplicateNameWarnings.includes(message)) {
283 duplicateNameWarnings.push(message);
284 console.warn(message);
285 }
286 }
287 }
288 }
289
290 emitter.emit({
291 type: 'state',
292 data: {
293 state
294 }
295 });
296
297 if (!isFirstMountRef.current && onStateChangeRef.current) {
298 onStateChangeRef.current(hydratedState);
299 }
300
301 isFirstMountRef.current = false;
302 }, [getRootState, emitter, state]);
303 const defaultOnUnhandledAction = React.useCallback(action => {
304 if (process.env.NODE_ENV === 'production') {
305 return;
306 }
307
308 const payload = action.payload;
309 let message = `The action '${action.type}'${payload ? ` with payload ${JSON.stringify(action.payload)}` : ''} was not handled by any navigator.`;
310
311 switch (action.type) {
312 case 'NAVIGATE':
313 case 'PUSH':
314 case 'REPLACE':
315 case 'JUMP_TO':
316 if (payload !== null && payload !== void 0 && payload.name) {
317 message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
318 } else {
319 message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
320 }
321
322 break;
323
324 case 'GO_BACK':
325 case 'POP':
326 case 'POP_TO_TOP':
327 message += `\n\nIs there any screen to go back to?`;
328 break;
329
330 case 'OPEN_DRAWER':
331 case 'CLOSE_DRAWER':
332 case 'TOGGLE_DRAWER':
333 message += `\n\nIs your screen inside a Drawer navigator?`;
334 break;
335 }
336
337 message += `\n\nThis is a development-only warning and won't be shown in production.`;
338 console.error(message);
339 }, []);
340 let element = /*#__PURE__*/React.createElement(NavigationContainerRefContext.Provider, {
341 value: navigation
342 }, /*#__PURE__*/React.createElement(ScheduleUpdateContext.Provider, {
343 value: scheduleContext
344 }, /*#__PURE__*/React.createElement(NavigationBuilderContext.Provider, {
345 value: builderContext
346 }, /*#__PURE__*/React.createElement(NavigationStateContext.Provider, {
347 value: context
348 }, /*#__PURE__*/React.createElement(UnhandledActionContext.Provider, {
349 value: onUnhandledAction !== null && onUnhandledAction !== void 0 ? onUnhandledAction : defaultOnUnhandledAction
350 }, /*#__PURE__*/React.createElement(EnsureSingleNavigator, null, children))))));
351
352 if (independent) {
353 // We need to clear any existing contexts for nested independent container to work correctly
354 element = /*#__PURE__*/React.createElement(NavigationRouteContext.Provider, {
355 value: undefined
356 }, /*#__PURE__*/React.createElement(NavigationContext.Provider, {
357 value: undefined
358 }, element));
359 }
360
361 return element;
362});
363export default BaseNavigationContainer;
364//# sourceMappingURL=BaseNavigationContainer.js.map
\No newline at end of file