1 | import { CommonActions } from '@react-navigation/routers';
|
2 | import * as React from 'react';
|
3 | import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
4 | import checkSerializable from './checkSerializable';
|
5 | import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
|
6 | import EnsureSingleNavigator from './EnsureSingleNavigator';
|
7 | import findFocusedRoute from './findFocusedRoute';
|
8 | import NavigationBuilderContext from './NavigationBuilderContext';
|
9 | import NavigationContainerRefContext from './NavigationContainerRefContext';
|
10 | import NavigationContext from './NavigationContext';
|
11 | import NavigationRouteContext from './NavigationRouteContext';
|
12 | import NavigationStateContext from './NavigationStateContext';
|
13 | import UnhandledActionContext from './UnhandledActionContext';
|
14 | import useChildListeners from './useChildListeners';
|
15 | import useEventEmitter from './useEventEmitter';
|
16 | import useKeyedChildListeners from './useKeyedChildListeners';
|
17 | import useOptionsGetters from './useOptionsGetters';
|
18 | import { ScheduleUpdateContext } from './useScheduleUpdate';
|
19 | import useSyncState from './useSyncState';
|
20 | const serializableWarnings = [];
|
21 | const duplicateNameWarnings = [];
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | const getPartialState = state => {
|
29 | if (state === undefined) {
|
30 | return;
|
31 | }
|
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 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | const BaseNavigationContainer = 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 (
|
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 = React.createElement(NavigationContainerRefContext.Provider, {
|
341 | value: navigation
|
342 | }, React.createElement(ScheduleUpdateContext.Provider, {
|
343 | value: scheduleContext
|
344 | }, React.createElement(NavigationBuilderContext.Provider, {
|
345 | value: builderContext
|
346 | }, React.createElement(NavigationStateContext.Provider, {
|
347 | value: context
|
348 | }, React.createElement(UnhandledActionContext.Provider, {
|
349 | value: onUnhandledAction !== null && onUnhandledAction !== void 0 ? onUnhandledAction : defaultOnUnhandledAction
|
350 | }, React.createElement(EnsureSingleNavigator, null, children))))));
|
351 |
|
352 | if (independent) {
|
353 |
|
354 | element = React.createElement(NavigationRouteContext.Provider, {
|
355 | value: undefined
|
356 | }, React.createElement(NavigationContext.Provider, {
|
357 | value: undefined
|
358 | }, element));
|
359 | }
|
360 |
|
361 | return element;
|
362 | });
|
363 | export default BaseNavigationContainer;
|
364 |
|
\ | No newline at end of file |