Skip to content

Commit

Permalink
Fix: fix the issue 10527
Browse files Browse the repository at this point in the history
  • Loading branch information
Yupeng Li committed Jun 9, 2022
1 parent 492e2a0 commit 7b12043
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 97 deletions.
41 changes: 30 additions & 11 deletions packages/core/src/BaseNavigationContainer.tsx
Expand Up @@ -14,7 +14,9 @@ import checkSerializable from './checkSerializable';
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import findFocusedRoute from './findFocusedRoute';
import NavigationBuilderContext from './NavigationBuilderContext';
import NavigationBuilderContext, {
FocusedNavigationListener,
} from './NavigationBuilderContext';
import NavigationContainerRefContext from './NavigationContainerRefContext';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
Expand Down Expand Up @@ -66,6 +68,18 @@ const getPartialState = (
};
};

/**
* Retrieves the first focus listener.
* @param keyedFocusListeners Keyed focus listeners.
*/
const getFirstFocusListener = (keyedFocusListeners: {
[key: string]: FocusedNavigationListener | undefined;
}): FocusedNavigationListener | undefined => {
return Object.values(keyedFocusListeners).filter(
(listener) => listener !== undefined
)[0];
};

/**
* Container component which holds the navigation state.
* This should be rendered at the root wrapping the whole app.
Expand Down Expand Up @@ -102,35 +116,38 @@ const BaseNavigationContainer = React.forwardRef(
const isFirstMountRef = React.useRef<boolean>(true);

const navigatorKeyRef = React.useRef<string | undefined>();

const getKey = React.useCallback(() => navigatorKeyRef.current, []);

const setKey = React.useCallback((key: string) => {
navigatorKeyRef.current = key;
}, []);

const { keyedListeners, addKeyedListener } = useKeyedChildListeners();
const focusListeners = [...keyedListeners.focus.values()];

const dispatch = React.useCallback(
(
action:
| NavigationAction
| ((state: NavigationState) => NavigationAction)
) => {
if (focusListeners[0] == null) {
const firstFocusListener = getFirstFocusListener(keyedListeners.focus);
if (firstFocusListener == null) {
console.error(NOT_INITIALIZED_ERROR);
} else {
focusListeners[0]((navigation) => navigation.dispatch(action));
firstFocusListener((navigation) => navigation.dispatch(action));
}
},
[keyedListeners.focus]
);

const canGoBack = React.useCallback(() => {
if (focusListeners[0] == null) {
const firstFocusListener = getFirstFocusListener(keyedListeners.focus);
if (firstFocusListener == null) {
return false;
}

const { result, handled } = focusListeners[0]((navigation) =>
const { result, handled } = firstFocusListener((navigation) =>
navigation.canGoBack()
);

Expand All @@ -143,13 +160,15 @@ const BaseNavigationContainer = React.forwardRef(

const resetRoot = React.useCallback(
(state?: PartialState<NavigationState> | NavigationState) => {
const target =
state?.key ?? keyedListeners.getState.get('root')?.().key;
const target = state?.key ?? keyedListeners.getState.root?.().key;

if (target == null) {
console.error(NOT_INITIALIZED_ERROR);
} else {
focusListeners[0]?.((navigation) =>
const firstFocusListener = getFirstFocusListener(
keyedListeners.focus
);
firstFocusListener?.((navigation) =>
navigation.dispatch({
...CommonActions.reset(state),
target,
Expand All @@ -161,7 +180,7 @@ const BaseNavigationContainer = React.forwardRef(
);

const getRootState = React.useCallback(() => {
return keyedListeners.getState.get('root')?.();
return keyedListeners.getState.root?.();
}, [keyedListeners.getState]);

const getCurrentRoute = React.useCallback(() => {
Expand Down Expand Up @@ -198,7 +217,7 @@ const BaseNavigationContainer = React.forwardRef(
getRootState,
getCurrentRoute,
getCurrentOptions,
isReady: () => focusListeners[0] != null,
isReady: () => getFirstFocusListener(keyedListeners.focus) != null,
}),
[
canGoBack,
Expand Down
12 changes: 5 additions & 7 deletions packages/core/src/NavigationBuilderContext.tsx
Expand Up @@ -7,19 +7,17 @@ import * as React from 'react';

import type { NavigationHelpers } from './types';

export type MapValueType<A> = A extends Map<any, infer V> ? V : never;

export type KeyedListenerMap = {
getState: Map<string, GetStateListener>;
beforeRemove: Map<string, ChildBeforeRemoveListener>;
action: Map<string, ChildActionListener>;
focus: Map<string, FocusedNavigationListener>;
action: ChildActionListener;
focus: FocusedNavigationListener;
getState: GetStateListener;
beforeRemove: ChildBeforeRemoveListener;
};

export type AddKeyedListener = <T extends keyof KeyedListenerMap>(
type: T,
key: string,
listener: MapValueType<KeyedListenerMap[T]> | undefined
listener: KeyedListenerMap[T]
) => void;

export type ChildActionListener = (
Expand Down
18 changes: 6 additions & 12 deletions packages/core/src/useFocusedListenersChildrenAdapter.tsx
Expand Up @@ -9,10 +9,8 @@ import type { NavigationHelpers } from './types';

type Options = {
navigation: NavigationHelpers<ParamListBase>;
focusedListeners: Map<
string | undefined,
FocusedNavigationListener | undefined
>;
focusedListeners: Record<string, FocusedNavigationListener | undefined>;
key?: string;
};

/**
Expand All @@ -21,21 +19,17 @@ type Options = {
export default function useFocusedListenersChildrenAdapter({
navigation,
focusedListeners,
key,
}: Options) {
const { addKeyedListener } = React.useContext(NavigationBuilderContext);

let listenerKey = 'undefined';
const listener = React.useCallback(
(callback: FocusedNavigationCallback<any>) => {
if (navigation.isFocused()) {
const listenerKeys = focusedListeners.keys();
for (const key of listenerKeys) {
const listener = focusedListeners.get(key);
for (const listener of Object.values(focusedListeners)) {
if (listener) {
const { handled, result } = listener(callback);

if (handled) {
listenerKey = key ?? 'undefined';
return { handled, result };
}
}
Expand All @@ -50,7 +44,7 @@ export default function useFocusedListenersChildrenAdapter({
);

React.useEffect(
() => addKeyedListener?.('focus', listenerKey, listener),
[addKeyedListener, listenerKey, listener]
() => addKeyedListener?.('focus', key ?? 'root', listener),
[addKeyedListener, listener, key]
);
}
43 changes: 19 additions & 24 deletions packages/core/src/useKeyedChildListeners.tsx
@@ -1,39 +1,34 @@
import * as React from 'react';

import type {
KeyedListenerMap,
MapValueType,
} from './NavigationBuilderContext';

type KeyedChildListeners = {
[ListenerType in keyof KeyedListenerMap]: Map<
string | undefined,
MapValueType<KeyedListenerMap[ListenerType]> | undefined
>;
};
import type { KeyedListenerMap } from './NavigationBuilderContext';

/**
* Hook which lets child navigators add getters to be called for obtaining rehydrated state.
* Hook which lets child navigators add action listener, focus listener,
* beforeRemove listener, and getters to be called for obtaining rehydrated state.
*/
export default function useKeyedChildListeners() {
const { current: keyedListeners } = React.useRef<KeyedChildListeners>({
getState: new Map(),
beforeRemove: new Map(),
action: new Map(),
focus: new Map(),
const { current: keyedListeners } = React.useRef<{
[K in keyof KeyedListenerMap]: Record<
string,
KeyedListenerMap[K] | undefined
>;
}>({
action: {},
focus: {},
getState: {},
beforeRemove: {},
});

const addKeyedListener = React.useCallback(
<ListenerType extends keyof KeyedChildListeners>(
type: ListenerType,
key: string | undefined,
listener: MapValueType<KeyedListenerMap[ListenerType]> | undefined
<T extends keyof KeyedListenerMap>(
type: T,
key: string,
listener: KeyedListenerMap[T]
) => {
const keyToSet = key !== undefined ? key : 'undefined';
keyedListeners[type].set(keyToSet, listener as any);
keyedListeners[type][key] = listener;

return () => {
keyedListeners[type].set(keyToSet, undefined);
keyedListeners[type][key] = undefined;
};
},
[keyedListeners]
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/useNavigationBuilder.tsx
Expand Up @@ -650,6 +650,7 @@ export default function useNavigationBuilder<
});

useFocusedListenersChildrenAdapter({
key: route?.key,
navigation,
focusedListeners: keyedListeners.focus,
});
Expand Down
50 changes: 20 additions & 30 deletions packages/core/src/useOnAction.tsx
Expand Up @@ -20,11 +20,8 @@ type Options = {
key?: string;
getState: () => NavigationState;
setState: (state: NavigationState | PartialState<NavigationState>) => void;
actionListeners: Map<string | undefined, ChildActionListener | undefined>;
beforeRemoveListeners: Map<
string | undefined,
ChildBeforeRemoveListener | undefined
>;
actionListeners: Record<string, ChildActionListener | undefined>;
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>;
routerConfigOptions: RouterConfigOptions;
emitter: NavigationEventEmitter<EventMapCore<any>>;
};
Expand Down Expand Up @@ -62,21 +59,6 @@ export default function useOnAction({
routerConfigOptionsRef.current = routerConfigOptions;
});

const sortKeys = React.useCallback(
(a, b) => {
const state = getState();
const focusedKey = state.routes[state.index].key;
if (a === focusedKey) {
return -1;
}
if (b === focusedKey) {
return +1;
}
return 0;
},
[getState]
);

const onAction = React.useCallback(
(
action: NavigationAction,
Expand Down Expand Up @@ -144,14 +126,23 @@ export default function useOnAction({
}
}

const actionListenerKeys = [...actionListeners.keys()]
.reverse()
.sort(sortKeys);
// If the action wasn't handled by current navigator or a parent navigator, let children handle it
return actionListenerKeys.some((actionListenerKey) => {
const listener = actionListeners.get(actionListenerKey);
return !!listener?.(action, visitedNavigators);
});
const currentFocusedRouteKey = state.routes[state.index].key;
const orderedActionListenerKeys = [
currentFocusedRouteKey,
...Object.keys(actionListeners)
.reverse()
.filter((listenerKey) => listenerKey !== currentFocusedRouteKey),
];

for (const listenerKey of orderedActionListenerKeys) {
const listener = actionListeners[listenerKey];
if (listener?.(action, visitedNavigators)) {
return true;
}
}

return false;
},
[
actionListeners,
Expand All @@ -164,7 +155,6 @@ export default function useOnAction({
onRouteFocusParent,
router,
setState,
sortKeys,
]
);

Expand All @@ -175,8 +165,8 @@ export default function useOnAction({
});

React.useEffect(
() => addListenerParent?.('action', key ?? 'undefined', onAction),
[addListenerParent, key, onAction]
() => addListenerParent?.('action', key ?? 'root', onAction),
[addListenerParent, onAction, key]
);

return onAction;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/useOnGetState.tsx
Expand Up @@ -9,7 +9,7 @@ import NavigationRouteContext from './NavigationRouteContext';

type Options = {
getState: () => NavigationState;
getStateListeners: Map<string | undefined, GetStateListener | undefined>;
getStateListeners: Record<string, GetStateListener | undefined>;
};

export default function useOnGetState({
Expand All @@ -25,7 +25,7 @@ export default function useOnGetState({

// Avoid returning new route objects if we don't need to
const routes = state.routes.map((route) => {
const childState = getStateListeners.get(route.key)?.();
const childState = getStateListeners[route.key]?.();

if (route.state === childState) {
return route;
Expand Down
14 changes: 3 additions & 11 deletions packages/core/src/useOnPreventRemove.tsx
Expand Up @@ -14,20 +14,14 @@ import type { NavigationEventEmitter } from './useEventEmitter';
type Options = {
getState: () => NavigationState;
emitter: NavigationEventEmitter<EventMapCore<any>>;
beforeRemoveListeners: Map<
string | undefined,
ChildBeforeRemoveListener | undefined
>;
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>;
};

const VISITED_ROUTE_KEYS = Symbol('VISITED_ROUTE_KEYS');

export const shouldPreventRemove = (
emitter: NavigationEventEmitter<EventMapCore<any>>,
beforeRemoveListeners: Map<
string | undefined,
ChildBeforeRemoveListener | undefined
>,
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>,
currentRoutes: { key: string }[],
nextRoutes: { key?: string | undefined }[],
action: NavigationAction
Expand Down Expand Up @@ -55,9 +49,7 @@ export const shouldPreventRemove = (
}

// First, we need to check if any child screens want to prevent it
const isPrevented = beforeRemoveListeners.get(route.key)?.(
beforeRemoveAction
);
const isPrevented = beforeRemoveListeners[route.key]?.(beforeRemoveAction);

if (isPrevented) {
return true;
Expand Down

0 comments on commit 7b12043

Please sign in to comment.