Skip to content

Commit

Permalink
refactor!: disable child navigators handling actions by default (#10905)
Browse files Browse the repository at this point in the history
Due to backward compatibility reasons, React Navigation 5 and 6 support navigating to a screen in a child navigator with `navigation.navigate(ScreenName)` syntax. But this is problematic with the new architecture - it only works if the navigator is already mounted, doesn't work with TypeScript, etc. That's why there's a special API to navigate to a nested screen (`navigation.navigate(ParentScreenName, { screen: ScreenName })`).

This drops this behavior and adds a prop to explicitly enable it to make it easier to migrate. This prop will be removed in the next major.
  • Loading branch information
satya164 committed Nov 28, 2022
1 parent 849268a commit 9a50e03
Show file tree
Hide file tree
Showing 23 changed files with 195 additions and 147 deletions.
57 changes: 26 additions & 31 deletions packages/core/src/BaseNavigationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import useLatestCallback from 'use-latest-callback';
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
import checkSerializable from './checkSerializable';
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
import DeprecatedNavigationInChildContext from './DeprecatedNavigationInChildContext';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import findFocusedRoute from './findFocusedRoute';
import NavigationBuilderContext from './NavigationBuilderContext';
import NavigationContainerRefContext from './NavigationContainerRefContext';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import NavigationIndependentTreeContext from './NavigationIndependentTreeContext';
import NavigationStateContext from './NavigationStateContext';
import type {
NavigationContainerEventMap,
Expand All @@ -29,6 +29,7 @@ import UnhandledActionContext from './UnhandledActionContext';
import useChildListeners from './useChildListeners';
import useEventEmitter from './useEventEmitter';
import useKeyedChildListeners from './useKeyedChildListeners';
import useNavigationIndependentTree from './useNavigationIndependentTree';
import useOptionsGetters from './useOptionsGetters';
import { ScheduleUpdateContext } from './useScheduleUpdate';
import useSyncState from './useSyncState';
Expand Down Expand Up @@ -86,16 +87,17 @@ const BaseNavigationContainer = React.forwardRef(
onStateChange,
onReady,
onUnhandledAction,
independent,
navigationInChildEnabled = false,
children,
}: NavigationContainerProps,
ref?: React.Ref<NavigationContainerRef<ParamListBase>>
) {
const parent = React.useContext(NavigationStateContext);
const independent = useNavigationIndependentTree();

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}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
"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, wrap the container in 'NavigationIndependentTree' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
);
}

Expand Down Expand Up @@ -428,34 +430,27 @@ const BaseNavigationContainer = React.forwardRef(
}
);

let element = (
<NavigationContainerRefContext.Provider value={navigation}>
<ScheduleUpdateContext.Provider value={scheduleContext}>
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<UnhandledActionContext.Provider
value={onUnhandledAction ?? defaultOnUnhandledAction}
>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</UnhandledActionContext.Provider>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
</ScheduleUpdateContext.Provider>
</NavigationContainerRefContext.Provider>
return (
<NavigationIndependentTreeContext.Provider value={false}>
<NavigationContainerRefContext.Provider value={navigation}>
<ScheduleUpdateContext.Provider value={scheduleContext}>
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<UnhandledActionContext.Provider
value={onUnhandledAction ?? defaultOnUnhandledAction}
>
<DeprecatedNavigationInChildContext.Provider
value={navigationInChildEnabled}
>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</DeprecatedNavigationInChildContext.Provider>
</UnhandledActionContext.Provider>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
</ScheduleUpdateContext.Provider>
</NavigationContainerRefContext.Provider>
</NavigationIndependentTreeContext.Provider>
);

if (independent) {
// We need to clear any existing contexts for nested independent container to work correctly
element = (
<NavigationRouteContext.Provider value={undefined}>
<NavigationContext.Provider value={undefined}>
{element}
</NavigationContext.Provider>
</NavigationRouteContext.Provider>
);
}

return element;
}
);

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/DeprecatedNavigationInChildContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from 'react';

/**
* Context which enables deprecated bubbling to child navigators.
*/
const DeprecatedNavigationInChildContext = React.createContext(false);

export default DeprecatedNavigationInChildContext;
25 changes: 25 additions & 0 deletions packages/core/src/NavigationIndependentTree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';

import NavigationContext from './NavigationContext';
import NavigationIndependentTreeContext from './NavigationIndependentTreeContext';
import NavigationRouteContext from './NavigationRouteContext';

/**
* Component to make the child navigation container independent of parent containers.
*/
export default function NavigationIndependentTree({
children,
}: {
children: React.ReactNode;
}) {
return (
// We need to clear any existing contexts for nested independent container to work correctly
<NavigationRouteContext.Provider value={undefined}>
<NavigationContext.Provider value={undefined}>
<NavigationIndependentTreeContext.Provider value>
{children}
</NavigationIndependentTreeContext.Provider>
</NavigationContext.Provider>
</NavigationRouteContext.Provider>
);
}
8 changes: 8 additions & 0 deletions packages/core/src/NavigationIndependentTreeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from 'react';

/**
* Context which marks the navigation tree as independent.
*/
const NavigationIndependentTreeContext = React.createContext(false);

export default NavigationIndependentTreeContext;

0 comments on commit 9a50e03

Please sign in to comment.