diff --git a/example/src/index.tsx b/example/src/index.tsx
index 4bcf7e29..2c69bb47 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -22,7 +22,7 @@ import {
InitialState,
useLinking,
NavigationContainerRef,
- NavigationNativeContainer,
+ NavigationContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
@@ -202,7 +202,7 @@ export default function App() {
{Platform.OS === 'ios' && (
)}
-
@@ -307,7 +307,7 @@ export default function App() {
)}
-
+
);
}
diff --git a/packages/core/src/BaseNavigationContainer.tsx b/packages/core/src/BaseNavigationContainer.tsx
new file mode 100644
index 00000000..32fed271
--- /dev/null
+++ b/packages/core/src/BaseNavigationContainer.tsx
@@ -0,0 +1,300 @@
+import * as React from 'react';
+import * as CommonActions from './CommonActions';
+import EnsureSingleNavigator from './EnsureSingleNavigator';
+import NavigationBuilderContext from './NavigationBuilderContext';
+import useFocusedListeners from './useFocusedListeners';
+import useDevTools from './useDevTools';
+import useStateGetters from './useStateGetters';
+import isSerializable from './isSerializable';
+
+import {
+ Route,
+ NavigationState,
+ InitialState,
+ PartialState,
+ NavigationAction,
+ NavigationContainerRef,
+ NavigationContainerProps,
+} from './types';
+import useEventEmitter from './useEventEmitter';
+
+type State = NavigationState | PartialState | undefined;
+
+const MISSING_CONTEXT_ERROR =
+ "We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
+
+export const NavigationStateContext = React.createContext<{
+ isDefault?: true;
+ state?: NavigationState | PartialState;
+ getState: () => NavigationState | PartialState | undefined;
+ setState: (
+ state: NavigationState | PartialState | undefined
+ ) => void;
+ key?: string;
+ performTransaction: (action: () => void) => void;
+}>({
+ isDefault: true,
+
+ get getState(): any {
+ throw new Error(MISSING_CONTEXT_ERROR);
+ },
+ get setState(): any {
+ throw new Error(MISSING_CONTEXT_ERROR);
+ },
+ get performTransaction(): any {
+ throw new Error(MISSING_CONTEXT_ERROR);
+ },
+});
+
+let hasWarnedForSerialization = false;
+
+/**
+ * Remove `key` and `routeNames` from the state objects recursively to get partial state.
+ *
+ * @param state Initial state object.
+ */
+const getPartialState = (
+ state: InitialState | undefined
+): PartialState | undefined => {
+ if (state === undefined) {
+ return;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { key, routeNames, ...partialState } = state;
+
+ // @ts-ignore
+ return {
+ ...partialState,
+ stale: true,
+ routes: state.routes.map(route => {
+ if (route.state === undefined) {
+ return route as Route & {
+ state?: PartialState;
+ };
+ }
+
+ return { ...route, state: getPartialState(route.state) };
+ }),
+ };
+};
+
+/**
+ * Container component which holds the navigation state.
+ * This should be rendered at the root wrapping the whole app.
+ *
+ * @param props.initialState Initial state object for the navigation tree.
+ * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
+ * @param props.children Child elements to render the content.
+ * @param props.ref Ref object which refers to the navigation object containing helper methods.
+ */
+const BaseNavigationContainer = React.forwardRef(
+ function BaseNavigationContainer(
+ {
+ initialState,
+ onStateChange,
+ independent,
+ children,
+ }: NavigationContainerProps,
+ ref: React.Ref
+ ) {
+ const parent = React.useContext(NavigationStateContext);
+
+ if (!parent.isDefault && !independent) {
+ 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}' explicitely."
+ );
+ }
+
+ const [state, setNavigationState] = React.useState(() =>
+ getPartialState(initialState == null ? undefined : initialState)
+ );
+
+ const navigationStateRef = React.useRef();
+ const transactionStateRef = React.useRef(null);
+ const isTransactionActiveRef = React.useRef(false);
+ const isFirstMountRef = React.useRef(true);
+ const skipTrackingRef = React.useRef(false);
+
+ const performTransaction = React.useCallback((callback: () => void) => {
+ if (isTransactionActiveRef.current) {
+ throw new Error(
+ "Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
+ );
+ }
+
+ setNavigationState((navigationState: State) => {
+ isTransactionActiveRef.current = true;
+ transactionStateRef.current = navigationState;
+
+ try {
+ callback();
+ } finally {
+ isTransactionActiveRef.current = false;
+ }
+
+ return transactionStateRef.current;
+ });
+ }, []);
+
+ const getState = React.useCallback(
+ () =>
+ transactionStateRef.current !== null
+ ? transactionStateRef.current
+ : navigationStateRef.current,
+ []
+ );
+
+ const setState = React.useCallback((navigationState: State) => {
+ if (transactionStateRef.current === null) {
+ throw new Error(
+ "Any 'setState' calls need to be done inside 'performTransaction'"
+ );
+ }
+
+ transactionStateRef.current = navigationState;
+ }, []);
+
+ const reset = React.useCallback(
+ (state: NavigationState) => {
+ performTransaction(() => {
+ skipTrackingRef.current = true;
+ setState(state);
+ });
+ },
+ [performTransaction, setState]
+ );
+
+ const { trackState, trackAction } = useDevTools({
+ name: '@react-navigation',
+ reset,
+ state,
+ });
+
+ const {
+ listeners,
+ addListener: addFocusedListener,
+ } = useFocusedListeners();
+
+ const { getStateForRoute, addStateGetter } = useStateGetters();
+
+ const dispatch = (
+ action: NavigationAction | ((state: NavigationState) => NavigationAction)
+ ) => {
+ listeners[0](navigation => navigation.dispatch(action));
+ };
+
+ const canGoBack = () => {
+ const { result, handled } = listeners[0](navigation =>
+ navigation.canGoBack()
+ );
+
+ if (handled) {
+ return result;
+ } else {
+ return false;
+ }
+ };
+
+ const resetRoot = React.useCallback(
+ (state?: PartialState | NavigationState) => {
+ performTransaction(() => {
+ trackAction('@@RESET_ROOT');
+ setState(state);
+ });
+ },
+ [performTransaction, setState, trackAction]
+ );
+
+ const getRootState = React.useCallback(() => {
+ return getStateForRoute('root');
+ }, [getStateForRoute]);
+
+ const emitter = useEventEmitter();
+
+ React.useImperativeHandle(ref, () => ({
+ ...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
+ any
+ >((acc, name) => {
+ acc[name] = (...args: any[]) =>
+ dispatch(
+ CommonActions[name](
+ // @ts-ignore
+ ...args
+ )
+ );
+ return acc;
+ }, {}),
+ ...emitter.create('root'),
+ resetRoot,
+ dispatch,
+ canGoBack,
+ getRootState,
+ }));
+
+ const builderContext = React.useMemo(
+ () => ({
+ addFocusedListener,
+ addStateGetter,
+ trackAction,
+ }),
+ [addFocusedListener, trackAction, addStateGetter]
+ );
+
+ const context = React.useMemo(
+ () => ({
+ state,
+ performTransaction,
+ getState,
+ setState,
+ }),
+ [getState, performTransaction, setState, state]
+ );
+
+ React.useEffect(() => {
+ if (process.env.NODE_ENV !== 'production') {
+ if (
+ state !== undefined &&
+ !isSerializable(state) &&
+ !hasWarnedForSerialization
+ ) {
+ hasWarnedForSerialization = true;
+
+ console.warn(
+ "We found non-serializable values in the navigation state, which 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 functions in your options, you can use 'navigation.setOptions' instead."
+ );
+ }
+ }
+
+ emitter.emit({
+ type: 'state',
+ data: { state },
+ });
+
+ if (skipTrackingRef.current) {
+ skipTrackingRef.current = false;
+ } else {
+ trackState(getRootState);
+ }
+
+ navigationStateRef.current = state;
+ transactionStateRef.current = null;
+
+ if (!isFirstMountRef.current && onStateChange) {
+ onStateChange(getRootState());
+ }
+
+ isFirstMountRef.current = false;
+ }, [state, onStateChange, trackState, getRootState, emitter]);
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+
+export default BaseNavigationContainer;
diff --git a/packages/core/src/NavigationContainer.tsx b/packages/core/src/NavigationContainer.tsx
deleted file mode 100644
index a61d6a52..00000000
--- a/packages/core/src/NavigationContainer.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-import * as React from 'react';
-import * as CommonActions from './CommonActions';
-import EnsureSingleNavigator from './EnsureSingleNavigator';
-import NavigationBuilderContext from './NavigationBuilderContext';
-import useFocusedListeners from './useFocusedListeners';
-import useDevTools from './useDevTools';
-import useStateGetters from './useStateGetters';
-import isSerializable from './isSerializable';
-
-import {
- Route,
- NavigationState,
- InitialState,
- PartialState,
- NavigationAction,
- NavigationContainerRef,
- NavigationContainerProps,
-} from './types';
-import useEventEmitter from './useEventEmitter';
-
-type State = NavigationState | PartialState | undefined;
-
-const MISSING_CONTEXT_ERROR =
- "We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
-
-export const NavigationStateContext = React.createContext<{
- isDefault?: true;
- state?: NavigationState | PartialState;
- getState: () => NavigationState | PartialState | undefined;
- setState: (
- state: NavigationState | PartialState | undefined
- ) => void;
- key?: string;
- performTransaction: (action: () => void) => void;
-}>({
- isDefault: true,
-
- get getState(): any {
- throw new Error(MISSING_CONTEXT_ERROR);
- },
- get setState(): any {
- throw new Error(MISSING_CONTEXT_ERROR);
- },
- get performTransaction(): any {
- throw new Error(MISSING_CONTEXT_ERROR);
- },
-});
-
-let hasWarnedForSerialization = false;
-
-/**
- * Remove `key` and `routeNames` from the state objects recursively to get partial state.
- *
- * @param state Initial state object.
- */
-const getPartialState = (
- state: InitialState | undefined
-): PartialState | undefined => {
- if (state === undefined) {
- return;
- }
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { key, routeNames, ...partialState } = state;
-
- // @ts-ignore
- return {
- ...partialState,
- stale: true,
- routes: state.routes.map(route => {
- if (route.state === undefined) {
- return route as Route & {
- state?: PartialState;
- };
- }
-
- return { ...route, state: getPartialState(route.state) };
- }),
- };
-};
-
-/**
- * Container component which holds the navigation state.
- * This should be rendered at the root wrapping the whole app.
- *
- * @param props.initialState Initial state object for the navigation tree.
- * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
- * @param props.children Child elements to render the content.
- * @param props.ref Ref object which refers to the navigation object containing helper methods.
- */
-const Container = React.forwardRef(function NavigationContainer(
- {
- initialState,
- onStateChange,
- independent,
- children,
- }: NavigationContainerProps,
- ref: React.Ref
-) {
- const parent = React.useContext(NavigationStateContext);
-
- if (!parent.isDefault && !independent) {
- 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}' explicitely."
- );
- }
-
- const [state, setNavigationState] = React.useState(() =>
- getPartialState(initialState == null ? undefined : initialState)
- );
-
- const navigationStateRef = React.useRef();
- const transactionStateRef = React.useRef(null);
- const isTransactionActiveRef = React.useRef(false);
- const isFirstMountRef = React.useRef(true);
- const skipTrackingRef = React.useRef(false);
-
- const performTransaction = React.useCallback((callback: () => void) => {
- if (isTransactionActiveRef.current) {
- throw new Error(
- "Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
- );
- }
-
- setNavigationState((navigationState: State) => {
- isTransactionActiveRef.current = true;
- transactionStateRef.current = navigationState;
-
- try {
- callback();
- } finally {
- isTransactionActiveRef.current = false;
- }
-
- return transactionStateRef.current;
- });
- }, []);
-
- const getState = React.useCallback(
- () =>
- transactionStateRef.current !== null
- ? transactionStateRef.current
- : navigationStateRef.current,
- []
- );
-
- const setState = React.useCallback((navigationState: State) => {
- if (transactionStateRef.current === null) {
- throw new Error(
- "Any 'setState' calls need to be done inside 'performTransaction'"
- );
- }
-
- transactionStateRef.current = navigationState;
- }, []);
-
- const reset = React.useCallback(
- (state: NavigationState) => {
- performTransaction(() => {
- skipTrackingRef.current = true;
- setState(state);
- });
- },
- [performTransaction, setState]
- );
-
- const { trackState, trackAction } = useDevTools({
- name: '@react-navigation',
- reset,
- state,
- });
-
- const { listeners, addListener: addFocusedListener } = useFocusedListeners();
-
- const { getStateForRoute, addStateGetter } = useStateGetters();
-
- const dispatch = (
- action: NavigationAction | ((state: NavigationState) => NavigationAction)
- ) => {
- listeners[0](navigation => navigation.dispatch(action));
- };
-
- const canGoBack = () => {
- const { result, handled } = listeners[0](navigation =>
- navigation.canGoBack()
- );
-
- if (handled) {
- return result;
- } else {
- return false;
- }
- };
-
- const resetRoot = React.useCallback(
- (state?: PartialState | NavigationState) => {
- performTransaction(() => {
- trackAction('@@RESET_ROOT');
- setState(state);
- });
- },
- [performTransaction, setState, trackAction]
- );
-
- const getRootState = React.useCallback(() => {
- return getStateForRoute('root');
- }, [getStateForRoute]);
-
- const emitter = useEventEmitter();
-
- React.useImperativeHandle(ref, () => ({
- ...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
- any
- >((acc, name) => {
- acc[name] = (...args: any[]) =>
- dispatch(
- CommonActions[name](
- // @ts-ignore
- ...args
- )
- );
- return acc;
- }, {}),
- ...emitter.create('root'),
- resetRoot,
- dispatch,
- canGoBack,
- getRootState,
- }));
-
- const builderContext = React.useMemo(
- () => ({
- addFocusedListener,
- addStateGetter,
- trackAction,
- }),
- [addFocusedListener, trackAction, addStateGetter]
- );
-
- const context = React.useMemo(
- () => ({
- state,
- performTransaction,
- getState,
- setState,
- }),
- [getState, performTransaction, setState, state]
- );
-
- React.useEffect(() => {
- if (process.env.NODE_ENV !== 'production') {
- if (
- state !== undefined &&
- !isSerializable(state) &&
- !hasWarnedForSerialization
- ) {
- hasWarnedForSerialization = true;
-
- console.warn(
- "We found non-serializable values in the navigation state, which 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 functions in your options, you can use 'navigation.setOptions' instead."
- );
- }
- }
-
- emitter.emit({
- type: 'state',
- data: { state },
- });
-
- if (skipTrackingRef.current) {
- skipTrackingRef.current = false;
- } else {
- trackState(getRootState);
- }
-
- navigationStateRef.current = state;
- transactionStateRef.current = null;
-
- if (!isFirstMountRef.current && onStateChange) {
- onStateChange(getRootState());
- }
-
- isFirstMountRef.current = false;
- }, [state, onStateChange, trackState, getRootState, emitter]);
-
- return (
-
-
- {children}
-
-
- );
-});
-
-export default Container;
diff --git a/packages/core/src/SceneView.tsx b/packages/core/src/SceneView.tsx
index 50801d36..5453e270 100644
--- a/packages/core/src/SceneView.tsx
+++ b/packages/core/src/SceneView.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { NavigationStateContext } from './NavigationContainer';
+import { NavigationStateContext } from './BaseNavigationContainer';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer';
diff --git a/packages/core/src/__tests__/NavigationContainer.test.tsx b/packages/core/src/__tests__/BaseNavigationContainer.test.tsx
similarity index 93%
rename from packages/core/src/__tests__/NavigationContainer.test.tsx
rename to packages/core/src/__tests__/BaseNavigationContainer.test.tsx
index 8f9ef281..f6b5adb4 100644
--- a/packages/core/src/__tests__/NavigationContainer.test.tsx
+++ b/packages/core/src/__tests__/BaseNavigationContainer.test.tsx
@@ -1,8 +1,8 @@
import * as React from 'react';
import { act, render } from 'react-native-testing-library';
-import NavigationContainer, {
+import BaseNavigationContainer, {
NavigationStateContext,
-} from '../NavigationContainer';
+} from '../BaseNavigationContainer';
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import useNavigationBuilder from '../useNavigationBuilder';
import Screen from '../Screen';
@@ -85,9 +85,9 @@ it('throws when setState is called outside performTransaction', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -112,9 +112,9 @@ it('throws when nesting performTransaction', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -125,11 +125,11 @@ it('throws when nesting performTransaction', () => {
it('throws when nesting containers', () => {
expect(() =>
render(
-
-
+
+
-
-
+
+
)
).toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
@@ -137,11 +137,11 @@ it('throws when nesting containers', () => {
expect(() =>
render(
-
-
+
+
-
-
+
+
)
).not.toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
@@ -223,7 +223,7 @@ it('handle dispatching with ref', () => {
};
const element = (
- {
)}
-
+
);
render(element).update(element);
@@ -301,7 +301,7 @@ it('handle resetting state with ref', () => {
const onStateChange = jest.fn();
const element = (
-
+
{() => null}
@@ -322,7 +322,7 @@ it('handle resetting state with ref', () => {
)}
-
+
);
render(element).update(element);
@@ -389,7 +389,7 @@ it('handles getRootState', () => {
const ref = React.createRef();
const element = (
-
+
{() => (
@@ -401,7 +401,7 @@ it('handles getRootState', () => {
{() => null}
-
+
);
render(element);
@@ -450,13 +450,13 @@ it('emits state events when the state changes', () => {
const ref = React.createRef();
const element = (
-
+
{() => null}
{() => null}
{() => null}
-
+
);
render(element).update(element);
diff --git a/packages/core/src/__tests__/CommonActions.test.tsx b/packages/core/src/__tests__/CommonActions.test.tsx
index 1bf3e57b..9967e2d9 100644
--- a/packages/core/src/__tests__/CommonActions.test.tsx
+++ b/packages/core/src/__tests__/CommonActions.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import Screen from '../Screen';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
@@ -26,7 +26,7 @@ it('throws if NAVIGATE dispatched neither key nor name', () => {
const onStateChange = jest.fn();
const element = (
-
+
{
initialParams={{ count: 10 }}
/>
-
+
);
expect(() => render(element).update(element)).toThrowError(
diff --git a/packages/core/src/__tests__/index.test.tsx b/packages/core/src/__tests__/index.test.tsx
index a18e8aee..88a4d8f1 100644
--- a/packages/core/src/__tests__/index.test.tsx
+++ b/packages/core/src/__tests__/index.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import Screen from '../Screen';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
@@ -28,7 +28,7 @@ it('initializes state for a navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
-
+
{
)}
-
+
);
render(element).update(element);
@@ -75,11 +75,11 @@ it("doesn't crash when initialState is null", () => {
const element = (
// @ts-ignore
-
+
-
+
);
expect(() => render(element)).not.toThrowError();
@@ -112,7 +112,7 @@ it('rehydrates state for a navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
-
@@ -120,7 +120,7 @@ it('rehydrates state for a navigator on navigation', () => {
-
+
);
render(element).update(element);
@@ -166,7 +166,7 @@ it("doesn't rehydrate state if the type of state didn't match router", () => {
const onStateChange = jest.fn();
const element = (
-
@@ -178,7 +178,7 @@ it("doesn't rehydrate state if the type of state didn't match router", () => {
/>
-
+
);
render(element).update(element);
@@ -219,7 +219,7 @@ it('initializes state for nested screens in React.Fragment', () => {
const onStateChange = jest.fn();
const element = (
-
+
@@ -227,7 +227,7 @@ it('initializes state for nested screens in React.Fragment', () => {
-
+
);
render(element).update(element);
@@ -266,7 +266,7 @@ it('initializes state for nested navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
-
+
@@ -278,7 +278,7 @@ it('initializes state for nested navigator on navigation', () => {
)}
-
+
);
render(element).update(element);
@@ -328,12 +328,12 @@ it("doesn't update state if nothing changed", () => {
const onStateChange = jest.fn();
render(
-
+
-
+
);
expect(onStateChange).toBeCalledTimes(0);
@@ -360,12 +360,12 @@ it("doesn't update state if action wasn't handled", () => {
const spy = jest.spyOn(console, 'error').mockImplementation();
render(
-
+
-
+
);
expect(onStateChange).toBeCalledTimes(0);
@@ -396,12 +396,12 @@ it('cleans up state when the navigator unmounts', () => {
const onStateChange = jest.fn();
const element = (
-
+
-
+
);
const root = render(element);
@@ -422,7 +422,7 @@ it('cleans up state when the navigator unmounts', () => {
});
root.update(
-
+
);
expect(onStateChange).toBeCalledTimes(2);
@@ -454,12 +454,12 @@ it('allows state updates by dispatching a function returning an action', () => {
const onStateChange = jest.fn();
const element = (
-
+
-
+
);
render(element).update(element);
@@ -496,12 +496,12 @@ it('updates route params with setParams', () => {
const onStateChange = jest.fn();
render(
-
+
-
+
);
act(() => setParams({ username: 'alice' }));
@@ -556,7 +556,7 @@ it('updates route params with setParams applied to parent', () => {
const onStateChange = jest.fn();
render(
-
+
{() => (
@@ -567,7 +567,7 @@ it('updates route params with setParams applied to parent', () => {
-
+
);
act(() => setParams({ username: 'alice' }));
@@ -634,22 +634,22 @@ it('handles change in route names', () => {
const onStateChange = jest.fn();
const root = render(
-
+
-
+
);
root.update(
-
+
-
+
);
expect(onStateChange).toBeCalledWith({
@@ -677,7 +677,7 @@ it('navigates to nested child in a navigator', () => {
const navigation = React.createRef();
const element = render(
-
+
{() => (
@@ -704,7 +704,7 @@ it('navigates to nested child in a navigator', () => {
)}
-
+
);
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
@@ -752,11 +752,11 @@ it('gives access to internal state', () => {
};
const root = (
-
+
-
+
);
render(root).update(root);
@@ -778,9 +778,9 @@ it("throws if navigator doesn't have any screens", () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -812,14 +812,14 @@ it('throws if multiple navigators rendered under one container', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -836,12 +836,12 @@ it('throws when Screen is not the direct children', () => {
const Bar = () => null;
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -856,12 +856,12 @@ it('throws when a React Element is not the direct children', () => {
};
const element = (
-
+
Hello world
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -877,7 +877,7 @@ it("doesn't throw when direct children is Screen or empty element", () => {
};
render(
-
+
{null}
@@ -885,7 +885,7 @@ it("doesn't throw when direct children is Screen or empty element", () => {
{false}
{true}
-
+
);
});
@@ -896,13 +896,13 @@ it('throws when multiple screens with same name are defined', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -917,20 +917,20 @@ it('switches rendered navigators', () => {
};
const root = render(
-
+
-
+
);
expect(() =>
root.update(
-
+
-
+
)
).not.toThrowError(
'Another navigator is already registered for this container.'
@@ -944,11 +944,11 @@ it('throws if no name is passed to Screen', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -963,11 +963,11 @@ it('throws if invalid name is passed to Screen', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -982,13 +982,13 @@ it('throws if both children and component are passed', () => {
};
const element = (
-
+
{jest.fn()}
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -1003,11 +1003,11 @@ it('throws descriptive error for undefined screen component', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -1022,11 +1022,11 @@ it('throws descriptive error for invalid screen component', () => {
};
const element = (
-
+
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -1041,11 +1041,11 @@ it('throws descriptive error for invalid children', () => {
};
const element = (
-
+
{[] as any}
-
+
);
expect(() => render(element).update(element)).toThrowError(
@@ -1060,13 +1060,13 @@ it("doesn't throw if children is null", () => {
};
const element = (
-
+
{null as any}
-
+
);
expect(() => render(element).update(element)).not.toThrowError();
diff --git a/packages/core/src/__tests__/useDescriptors.test.tsx b/packages/core/src/__tests__/useDescriptors.test.tsx
index e22ccbfe..4107b019 100644
--- a/packages/core/src/__tests__/useDescriptors.test.tsx
+++ b/packages/core/src/__tests__/useDescriptors.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter, {
MockActions,
@@ -34,7 +34,7 @@ it('sets options with options prop as an object', () => {
const TestScreen = (): any => 'Test screen';
const root = render(
-
+
{
/>
-
+
);
expect(root).toMatchInlineSnapshot(`
@@ -79,7 +79,7 @@ it('sets options with options prop as a fuction', () => {
const TestScreen = (): any => 'Test screen';
const root = render(
-
+
{
/>
-
+
);
expect(root).toMatchInlineSnapshot(`
@@ -134,12 +134,12 @@ it('sets options with screenOptions prop as an object', () => {
const TestScreenB = (): any => 'Test screen B';
const root = render(
-
+
-
+
);
expect(root).toMatchInlineSnapshot(`
@@ -194,7 +194,7 @@ it('sets options with screenOptions prop as a fuction', () => {
const TestScreenB = (): any => 'Test screen B';
const root = render(
-
+
({
title: `${route.name}: ${route.params.author || route.params.fruit}`,
@@ -211,7 +211,7 @@ it('sets options with screenOptions prop as a fuction', () => {
initialParams={{ fruit: 'Apple' }}
/>
-
+
);
expect(root).toMatchInlineSnapshot(`
@@ -266,14 +266,14 @@ it('sets initial options with setOptions', () => {
};
const root = render(
-
+
{props => }
-
+
);
expect(root).toMatchInlineSnapshot(`
@@ -331,14 +331,14 @@ it('updates options with setOptions', () => {
};
const element = (
-
+
{props => }
-
+
);
const root = render(element);
@@ -396,7 +396,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
};
const root = (
-
+
{
/>
-
+
);
render(root).update(root);
@@ -452,11 +452,11 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
};
const root = (
-
+
-
+
);
render(root).update(root);
@@ -513,7 +513,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
};
const root = (
-
+
{() => (
@@ -523,7 +523,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
)}
-
+
);
render(root).update(root);
@@ -580,7 +580,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
};
const root = (
-
+
{() => (
@@ -597,7 +597,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
)}
-
+
);
render(root).update(root);
diff --git a/packages/core/src/__tests__/useEventEmitter.test.tsx b/packages/core/src/__tests__/useEventEmitter.test.tsx
index c6a3124c..922f703a 100644
--- a/packages/core/src/__tests__/useEventEmitter.test.tsx
+++ b/packages/core/src/__tests__/useEventEmitter.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { Router, NavigationState } from '../types';
@@ -47,7 +47,7 @@ it('fires focus and blur events in root navigator', () => {
const navigation = React.createRef();
const element = (
-
+
{
component={createComponent(fourthFocusCallback, fourthBlurCallback)}
/>
-
+
);
render(element);
@@ -139,7 +139,7 @@ it('fires focus and blur events in nested navigator', () => {
const child = React.createRef();
const element = (
-
+
{
)}
-
+
);
render(element);
@@ -307,12 +307,12 @@ it('fires blur event when a route is removed with a delay', async () => {
const navigation = React.createRef();
const element = (
-
+
-
+
);
render(element);
@@ -363,13 +363,13 @@ it('fires custom events', () => {
const ref = React.createRef();
const element = (
-
+
-
+
);
render(element);
@@ -444,11 +444,11 @@ it('has option to prevent default', () => {
const ref = React.createRef();
const element = (
-
+
-
+
);
render(element);
diff --git a/packages/core/src/__tests__/useFocusEffect.test.tsx b/packages/core/src/__tests__/useFocusEffect.test.tsx
index bd080715..845dbf1d 100644
--- a/packages/core/src/__tests__/useFocusEffect.test.tsx
+++ b/packages/core/src/__tests__/useFocusEffect.test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useFocusEffect from '../useFocusEffect';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -31,13 +31,13 @@ it('runs focus effect on focus change', () => {
const navigation = React.createRef();
const element = (
-
+
{() => null}
{() => null}
-
+
);
render(element);
@@ -84,12 +84,12 @@ it('runs focus effect on deps change', () => {
};
const App = ({ count }: { count: number }) => (
-
+
{() => }
{() => null}
-
+
);
const root = render();
@@ -135,13 +135,13 @@ it('runs focus effect when initial state is given', () => {
const navigation = React.createRef();
const element = (
-
+
{() => null}
{() => null}
-
+
);
render(element);
@@ -178,13 +178,13 @@ it('runs focus effect when only focused route is rendered', () => {
const navigation = React.createRef();
const element = (
-
+
{() => null}
{() => null}
-
+
);
render(element);
@@ -221,12 +221,12 @@ it('runs cleanup when component is unmounted', () => {
const TestB = () => null;
const App = ({ mounted }: { mounted: boolean }) => (
-
+
{() => null}
-
+
);
const root = render();
diff --git a/packages/core/src/__tests__/useIsFocused.test.tsx b/packages/core/src/__tests__/useIsFocused.test.tsx
index b5c429ff..80a6a661 100644
--- a/packages/core/src/__tests__/useIsFocused.test.tsx
+++ b/packages/core/src/__tests__/useIsFocused.test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useIsFocused from '../useIsFocused';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -24,13 +24,13 @@ it('renders correct focus state', () => {
const navigation = React.createRef();
const root = render(
-
+
{() => null}
{() => null}
-
+
);
expect(root).toMatchInlineSnapshot(`"unfocused"`);
diff --git a/packages/core/src/__tests__/useNavigation.test.tsx b/packages/core/src/__tests__/useNavigation.test.tsx
index 3321c9ab..b3cdecc0 100644
--- a/packages/core/src/__tests__/useNavigation.test.tsx
+++ b/packages/core/src/__tests__/useNavigation.test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -24,11 +24,11 @@ it('gets navigation prop from context', () => {
};
render(
-
+
-
+
);
});
@@ -50,7 +50,7 @@ it("gets navigation's parent from context", () => {
};
render(
-
+
{() => (
@@ -60,7 +60,7 @@ it("gets navigation's parent from context", () => {
)}
-
+
);
});
@@ -86,7 +86,7 @@ it("gets navigation's parent's parent from context", () => {
};
render(
-
+
{() => (
@@ -102,7 +102,7 @@ it("gets navigation's parent's parent from context", () => {
)}
-
+
);
});
diff --git a/packages/core/src/__tests__/useNavigationState.test.tsx b/packages/core/src/__tests__/useNavigationState.test.tsx
index 67ef8e8b..abee7882 100644
--- a/packages/core/src/__tests__/useNavigationState.test.tsx
+++ b/packages/core/src/__tests__/useNavigationState.test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigationState from '../useNavigationState';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
@@ -27,13 +27,13 @@ it('gets the current navigation state', () => {
const navigation = React.createRef();
const element = (
-
+
{() => null}
{() => null}
-
+
);
render(element);
@@ -78,13 +78,13 @@ it('gets the current navigation state with selector', () => {
const navigation = React.createRef();
const element = (
-
+
{() => null}
{() => null}
-
+
);
render(element);
@@ -133,13 +133,13 @@ it('gets the correct value if selector changes', () => {
const App = ({ selector }: { selector: (state: NavigationState) => any }) => {
return (
-
+
{() => null}
{() => null}
-
+
);
};
diff --git a/packages/core/src/__tests__/useOnAction.test.tsx b/packages/core/src/__tests__/useOnAction.test.tsx
index da072787..726215c7 100644
--- a/packages/core/src/__tests__/useOnAction.test.tsx
+++ b/packages/core/src/__tests__/useOnAction.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter, {
MockActions,
@@ -58,7 +58,7 @@ it("lets parent handle the action if child didn't", () => {
const onStateChange = jest.fn();
render(
-
+
{() => null}
{() => null}
@@ -70,7 +70,7 @@ it("lets parent handle the action if child didn't", () => {
)}
-
+
);
expect(onStateChange).toBeCalledTimes(1);
@@ -171,7 +171,7 @@ it("lets children handle the action if parent didn't", () => {
};
const element = (
-
@@ -187,7 +187,7 @@ it("lets children handle the action if parent didn't", () => {
)}
-
+
);
render(element).update(element);
@@ -284,7 +284,7 @@ it("action doesn't bubble if target is specified", () => {
const onStateChange = jest.fn();
const element = (
-
+
{() => null}
@@ -297,7 +297,7 @@ it("action doesn't bubble if target is specified", () => {
)}
-
+
);
render(element).update(element);
@@ -349,7 +349,7 @@ it('logs error if no navigator handled the action', () => {
};
const element = (
-
+
{() => null}
@@ -362,7 +362,7 @@ it('logs error if no navigator handled the action', () => {
)}
-
+
);
const spy = jest.spyOn(console, 'error').mockImplementation();
diff --git a/packages/core/src/__tests__/useRoute.test.tsx b/packages/core/src/__tests__/useRoute.test.tsx
index 4743aefe..1a43f98e 100644
--- a/packages/core/src/__tests__/useRoute.test.tsx
+++ b/packages/core/src/__tests__/useRoute.test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useRoute from '../useRoute';
-import NavigationContainer from '../NavigationContainer';
+import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { RouteProp } from '../types';
@@ -25,10 +25,10 @@ it('gets route prop from context', () => {
};
render(
-
+
-
+
);
});
diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx
index c8ab7d59..88274e3f 100644
--- a/packages/core/src/index.tsx
+++ b/packages/core/src/index.tsx
@@ -3,7 +3,7 @@ import * as CommonActions from './CommonActions';
export { CommonActions };
export { default as BaseRouter } from './BaseRouter';
-export { default as NavigationContainer } from './NavigationContainer';
+export { default as BaseNavigationContainer } from './BaseNavigationContainer';
export { default as createNavigatorFactory } from './createNavigatorFactory';
export { default as NavigationContext } from './NavigationContext';
diff --git a/packages/core/src/useNavigationBuilder.tsx b/packages/core/src/useNavigationBuilder.tsx
index c54a6cce..604a4d8b 100644
--- a/packages/core/src/useNavigationBuilder.tsx
+++ b/packages/core/src/useNavigationBuilder.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { isValidElementType } from 'react-is';
-import { NavigationStateContext } from './NavigationContainer';
+import { NavigationStateContext } from './BaseNavigationContainer';
import NavigationRouteContext from './NavigationRouteContext';
import Screen from './Screen';
import { navigate } from './CommonActions';
diff --git a/packages/core/src/useNavigationHelpers.tsx b/packages/core/src/useNavigationHelpers.tsx
index cdba2965..e0b23691 100644
--- a/packages/core/src/useNavigationHelpers.tsx
+++ b/packages/core/src/useNavigationHelpers.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as CommonActions from './CommonActions';
import NavigationContext from './NavigationContext';
-import { NavigationStateContext } from './NavigationContainer';
+import { NavigationStateContext } from './BaseNavigationContainer';
import { NavigationEventEmitter } from './useEventEmitter';
import {
NavigationHelpers,
diff --git a/packages/native/src/NavigationContainer.tsx b/packages/native/src/NavigationContainer.tsx
new file mode 100644
index 00000000..436e9824
--- /dev/null
+++ b/packages/native/src/NavigationContainer.tsx
@@ -0,0 +1,44 @@
+import * as React from 'react';
+import {
+ BaseNavigationContainer,
+ NavigationContainerProps,
+ NavigationContainerRef,
+} from '@react-navigation/core';
+import ThemeProvider from './theming/ThemeProvider';
+import DefaultTheme from './theming/DefaultTheme';
+import useBackButton from './useBackButton';
+import { Theme } from './types';
+
+type Props = NavigationContainerProps & {
+ theme?: Theme;
+};
+
+/**
+ * Container component which holds the navigation state
+ * designed for mobile apps.
+ * This should be rendered at the root wrapping the whole app.
+ *
+ * @param props.initialState Initial state object for the navigation tree.
+ * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
+ * @param props.theme Theme object for the navigators.
+ * @param props.children Child elements to render the content.
+ * @param props.ref Ref object which refers to the navigation object containing helper methods.
+ */
+const NavigationContainer = React.forwardRef(function NavigationContainer(
+ { theme = DefaultTheme, ...rest }: Props,
+ ref: React.Ref
+) {
+ const refContainer = React.useRef(null);
+
+ useBackButton(refContainer);
+
+ React.useImperativeHandle(ref, () => refContainer.current);
+
+ return (
+
+
+
+ );
+});
+
+export default NavigationContainer;
diff --git a/packages/native/src/NavigationNativeContainer.tsx b/packages/native/src/NavigationNativeContainer.tsx
index d40a75c2..612261e5 100644
--- a/packages/native/src/NavigationNativeContainer.tsx
+++ b/packages/native/src/NavigationNativeContainer.tsx
@@ -1,44 +1,5 @@
-import * as React from 'react';
-import {
- NavigationContainer,
- NavigationContainerProps,
- NavigationContainerRef,
-} from '@react-navigation/core';
-import ThemeProvider from './theming/ThemeProvider';
-import DefaultTheme from './theming/DefaultTheme';
-import useBackButton from './useBackButton';
-import { Theme } from './types';
-
-type Props = NavigationContainerProps & {
- theme?: Theme;
-};
-
-/**
- * Container component which holds the navigation state
- * designed for mobile apps.
- * This should be rendered at the root wrapping the whole app.
- *
- * @param props.initialState Initial state object for the navigation tree.
- * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
- * @param props.theme Theme object for the navigators.
- * @param props.children Child elements to render the content.
- * @param props.ref Ref object which refers to the navigation object containing helper methods.
- */
-const NavigationNativeContainer = React.forwardRef(function NativeContainer(
- { theme = DefaultTheme, ...rest }: Props,
- ref: React.Ref
-) {
- const refContainer = React.useRef(null);
-
- useBackButton(refContainer);
-
- React.useImperativeHandle(ref, () => refContainer.current);
-
- return (
-
-
-
+export default function() {
+ throw new Error(
+ "'NavigationNativeContainer' has been renamed to 'NavigationContainer"
);
-});
-
-export default NavigationNativeContainer;
+}
diff --git a/packages/native/src/index.tsx b/packages/native/src/index.tsx
index 8d52375d..852b5485 100644
--- a/packages/native/src/index.tsx
+++ b/packages/native/src/index.tsx
@@ -1,5 +1,6 @@
export * from '@react-navigation/core';
+export { default as NavigationContainer } from './NavigationContainer';
export { default as NavigationNativeContainer } from './NavigationNativeContainer';
export { default as useBackButton } from './useBackButton';