diff --git a/packages/core/src/NavigationContainer.tsx b/packages/core/src/NavigationContainer.tsx index b71f4058..cfe63e53 100644 --- a/packages/core/src/NavigationContainer.tsx +++ b/packages/core/src/NavigationContainer.tsx @@ -23,6 +23,7 @@ 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: ( @@ -31,6 +32,8 @@ export const NavigationStateContext = React.createContext<{ key?: string; performTransaction: (action: () => void) => void; }>({ + isDefault: true, + get getState(): any { throw new Error(MISSING_CONTEXT_ERROR); }, @@ -83,9 +86,22 @@ const getPartialState = ( * @param props.ref Ref object which refers to the navigation object containing helper methods. */ const Container = React.forwardRef(function NavigationContainer( - { initialState, onStateChange, children }: NavigationContainerProps, + { + 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) ); diff --git a/packages/core/src/__tests__/NavigationContainer.test.tsx b/packages/core/src/__tests__/NavigationContainer.test.tsx index 42ec38f5..904489ee 100644 --- a/packages/core/src/__tests__/NavigationContainer.test.tsx +++ b/packages/core/src/__tests__/NavigationContainer.test.tsx @@ -122,6 +122,32 @@ it('throws when nesting performTransaction', () => { ); }); +it('throws when nesting containers', () => { + expect(() => + render( + + + + + + ) + ).toThrowError( + "Looks like you have nested a 'NavigationContainer' inside another." + ); + + expect(() => + render( + + + + + + ) + ).not.toThrowError( + "Looks like you have nested a 'NavigationContainer' inside another." + ); +}); + it('handle dispatching with ref', () => { const CurrentParentRouter = MockRouter; diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 7210b6b9..d4b1ef9e 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -377,10 +377,23 @@ export type NavigationHelpers< }; export type NavigationContainerProps = { + /** + * Initial navigation state for the child navigators. + */ initialState?: InitialState; - onStateChange?: ( - state: NavigationState | PartialState | undefined - ) => void; + /** + * Callback which is called with the latest navigation state when it changes. + */ + onStateChange?: (state: NavigationState | undefined) => void; + /** + * Whether this navigation container should be independent of parent containers. + * If this is not set to `true`, this container cannot be nested inside another container. + * Setting it to `true` disconnects any children navigators from parent container. + */ + independent?: boolean; + /** + * Children elements to render. + */ children: React.ReactNode; };