mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-12 09:21:09 +08:00
feat: let child navigators handle actions from parent
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
This commit is contained in:
committed by
satyajit.happy
parent
f383d131d9
commit
ea8655252d
@@ -211,6 +211,35 @@ const StackRouter: Router<CommonAction | Action> = {
|
||||
}
|
||||
},
|
||||
|
||||
getStateForChildUpdate(state, { update, focus, key }) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
|
||||
if (index === -1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: focus ? index : state.index,
|
||||
routes: focus
|
||||
? [
|
||||
...state.routes.slice(0, index),
|
||||
{ ...state.routes[index], state: update },
|
||||
]
|
||||
: state.routes.map((route, i) =>
|
||||
i === index ? { ...route, state: update } : route
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
shouldActionPropagateToChildren(action) {
|
||||
return action.type === 'NAVIGATE';
|
||||
},
|
||||
|
||||
shouldActionChangeFocus(action) {
|
||||
return action.type === 'NAVIGATE';
|
||||
},
|
||||
|
||||
actionCreators: {
|
||||
push(name: string, params?: object) {
|
||||
return { type: 'PUSH', payload: { name, params } };
|
||||
|
||||
@@ -137,6 +137,30 @@ const TabRouter: Router<Action | CommonAction> = {
|
||||
}
|
||||
},
|
||||
|
||||
getStateForChildUpdate(state, { update, focus, key }) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
|
||||
if (index === -1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: focus ? index : state.index,
|
||||
routes: state.routes.map((route, i) =>
|
||||
i === index ? { ...route, state: update } : route
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
shouldActionPropagateToChildren(action) {
|
||||
return action.type === 'NAVIGATE';
|
||||
},
|
||||
|
||||
shouldActionChangeFocus(action) {
|
||||
return action.type === 'NAVIGATE';
|
||||
},
|
||||
|
||||
actionCreators: {
|
||||
jumpTo(name: string, params?: object) {
|
||||
return { type: 'JUMP_TO', payload: { name, params } };
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationHelpers, NavigationAction } from './types';
|
||||
import { NavigationHelpers, NavigationAction, NavigationState } from './types';
|
||||
|
||||
export type ChildActionListener = (
|
||||
action: NavigationAction,
|
||||
sourceRouteKey?: string
|
||||
) => boolean;
|
||||
|
||||
const NavigationBuilderContext = React.createContext<{
|
||||
helpers?: NavigationHelpers;
|
||||
onAction?: (action: NavigationAction) => boolean;
|
||||
onAction?: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
|
||||
addActionListener?: (listener: ChildActionListener) => void;
|
||||
removeActionListener?: (listener: ChildActionListener) => void;
|
||||
onChildUpdate?: (
|
||||
state: NavigationState,
|
||||
focus: boolean,
|
||||
key: string | undefined
|
||||
) => void;
|
||||
}>({});
|
||||
|
||||
export default NavigationBuilderContext;
|
||||
|
||||
@@ -19,6 +19,7 @@ export const NavigationStateContext = React.createContext<{
|
||||
state?: NavigationState | PartialState;
|
||||
getState: () => NavigationState | PartialState | undefined;
|
||||
setState: (state: NavigationState | undefined) => void;
|
||||
key?: string;
|
||||
}>({
|
||||
get getState(): any {
|
||||
throw new Error(MISSING_CONTEXT_ERROR);
|
||||
|
||||
@@ -42,8 +42,9 @@ export default function SceneView(props: Props) {
|
||||
|
||||
const getCurrentState = React.useCallback(() => {
|
||||
const state = getState();
|
||||
const currentRoute = state.routes.find(r => r.key === route.key);
|
||||
|
||||
return state.routes.find(r => r.key === route.key)!.state;
|
||||
return currentRoute ? currentRoute.state : undefined;
|
||||
}, [getState, route.key]);
|
||||
|
||||
const setCurrentState = React.useCallback(
|
||||
@@ -65,8 +66,9 @@ export default function SceneView(props: Props) {
|
||||
state: route.state,
|
||||
getState: getCurrentState,
|
||||
setState: setCurrentState,
|
||||
key: route.key,
|
||||
}),
|
||||
[getCurrentState, route.state, setCurrentState]
|
||||
[getCurrentState, route.key, route.state, setCurrentState]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,9 @@ import NavigationContainer from '../NavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import { Router } from '../types';
|
||||
|
||||
const MockRouter: Router<{ type: string }> = {
|
||||
let key = 0;
|
||||
|
||||
export const MockRouter: Router<{ type: string }> = {
|
||||
getInitialState({
|
||||
routeNames,
|
||||
initialRouteName = routeNames[0],
|
||||
@@ -14,7 +16,7 @@ const MockRouter: Router<{ type: string }> = {
|
||||
const index = routeNames.indexOf(initialRouteName);
|
||||
|
||||
return {
|
||||
key: 'root',
|
||||
key: String(key++),
|
||||
index,
|
||||
routeNames,
|
||||
routes: routeNames.map(name => ({
|
||||
@@ -32,7 +34,7 @@ const MockRouter: Router<{ type: string }> = {
|
||||
state = {
|
||||
...state,
|
||||
routeNames: state.routeNames || routeNames,
|
||||
key: state.key || 'root',
|
||||
key: state.key || String(key++),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,9 +54,35 @@ const MockRouter: Router<{ type: string }> = {
|
||||
}
|
||||
},
|
||||
|
||||
getStateForChildUpdate(state, { update, focus, key }) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
|
||||
if (index === -1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: focus ? index : state.index,
|
||||
routes: state.routes.map((route, i) =>
|
||||
i === index ? { ...route, state: update } : route
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
shouldActionPropagateToChildren() {
|
||||
return false;
|
||||
},
|
||||
|
||||
shouldActionChangeFocus() {
|
||||
return false;
|
||||
},
|
||||
|
||||
actionCreators: {},
|
||||
};
|
||||
|
||||
beforeEach(() => (key = 0));
|
||||
|
||||
it('initializes state for a navigator on navigation', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
@@ -100,7 +128,7 @@ it('initializes state for a navigator on navigation', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
index: 0,
|
||||
key: 'root',
|
||||
key: '1',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { count: 10 } },
|
||||
@@ -151,7 +179,7 @@ it('rehydrates state for a navigator on navigation', () => {
|
||||
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 1,
|
||||
key: 'root',
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
|
||||
});
|
||||
@@ -198,7 +226,7 @@ it('initializes state for nested navigator on navigation', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
index: 2,
|
||||
key: 'root',
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo' },
|
||||
@@ -208,7 +236,7 @@ it('initializes state for nested navigator on navigation', () => {
|
||||
name: 'baz',
|
||||
state: {
|
||||
index: 0,
|
||||
key: 'root',
|
||||
key: '3',
|
||||
routeNames: ['qux'],
|
||||
routes: [{ key: 'qux', name: 'qux' }],
|
||||
},
|
||||
@@ -317,7 +345,7 @@ it('cleans up state when the navigator unmounts', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: 'root',
|
||||
key: '1',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
|
||||
});
|
||||
@@ -396,7 +424,7 @@ it("lets parent handle the action if child didn't", () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 2,
|
||||
key: 'root',
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
routes: [
|
||||
{ key: 'baz', name: 'baz' },
|
||||
@@ -446,7 +474,7 @@ it('allows arbitrary state updates by dispatching a function', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
index: 1,
|
||||
key: 'root',
|
||||
key: '1',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [{ key: 'bar', name: 'bar' }, { key: 'foo', name: 'foo' }],
|
||||
});
|
||||
@@ -485,7 +513,7 @@ it('updates route params with setParams', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: 'root',
|
||||
key: '2',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
|
||||
@@ -498,7 +526,7 @@ it('updates route params with setParams', () => {
|
||||
expect(onStateChange).toBeCalledTimes(2);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: 'root',
|
||||
key: '2',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
|
||||
|
||||
129
src/__tests__/useOnChildUpdate.test.tsx
Normal file
129
src/__tests__/useOnChildUpdate.test.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-native-testing-library';
|
||||
import { Router } from '../types';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import { MockRouter } from './index.test';
|
||||
|
||||
it("lets children handle the action if parent didn't", () => {
|
||||
const ParentRouter: Router<{ type: string }> = {
|
||||
...MockRouter,
|
||||
|
||||
shouldActionPropagateToChildren() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
const ChildRouter: Router<{ type: string }> = {
|
||||
...MockRouter,
|
||||
|
||||
shouldActionChangeFocus() {
|
||||
return true;
|
||||
},
|
||||
|
||||
getStateForAction(state, action) {
|
||||
if (action.type === 'REVERSE') {
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.slice().reverse(),
|
||||
};
|
||||
}
|
||||
|
||||
return MockRouter.getStateForAction(state, action);
|
||||
},
|
||||
};
|
||||
|
||||
const ChildNavigator = (props: any) => {
|
||||
const { navigation, descriptors } = useNavigationBuilder(
|
||||
ChildRouter,
|
||||
props
|
||||
);
|
||||
|
||||
return descriptors[
|
||||
navigation.state.routes[navigation.state.index].key
|
||||
].render();
|
||||
};
|
||||
|
||||
const ParentNavigator = (props: any) => {
|
||||
const { navigation, descriptors } = useNavigationBuilder(
|
||||
ParentRouter,
|
||||
props
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{navigation.state.routes.map(route => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = (props: any) => {
|
||||
React.useEffect(() => {
|
||||
props.navigation.dispatch({ type: 'REVERSE' });
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const initialState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
key: 'baz',
|
||||
name: 'baz',
|
||||
state: {
|
||||
index: 0,
|
||||
key: '3',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [{ key: 'qux', name: 'qux' },{ key: 'lex', name: 'lex' }],
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
};
|
||||
|
||||
render(
|
||||
<NavigationContainer
|
||||
initialState={initialState}
|
||||
onStateChange={onStateChange}
|
||||
>
|
||||
<ParentNavigator>
|
||||
<Screen name="foo">{() => null}</Screen>
|
||||
<Screen name="bar">{TestScreen}</Screen>
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<ChildNavigator>
|
||||
<Screen name="qux" component={() => null} />
|
||||
<Screen name="lex" component={() => null} />
|
||||
</ChildNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</ParentNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: '2',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
routes: [
|
||||
{
|
||||
key: 'baz',
|
||||
name: 'baz',
|
||||
state: {
|
||||
index: 0,
|
||||
key: '3',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [{ key: 'lex', name: 'lex' }, { key: 'qux', name: 'qux' }],
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -87,6 +87,34 @@ export type Router<Action extends NavigationAction = CommonAction> = {
|
||||
action: Action
|
||||
): NavigationState | null;
|
||||
|
||||
getStateForChildUpdate(
|
||||
state: NavigationState,
|
||||
payload: {
|
||||
update: NavigationState;
|
||||
focus: boolean;
|
||||
key: string | undefined;
|
||||
}
|
||||
): NavigationState;
|
||||
|
||||
/**
|
||||
* Whether the action bubbles to other navigators
|
||||
* When an action isn't handled by current navigator, it can be passed to nested navigators
|
||||
*/
|
||||
shouldActionPropagateToChildren(
|
||||
action: NavigationAction,
|
||||
navigatorKey: string,
|
||||
sourceNavigatorKey?: string
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* Whether the action should also change focus in parent navigator
|
||||
*/
|
||||
shouldActionChangeFocus(
|
||||
action: NavigationAction,
|
||||
navigatorKey: string,
|
||||
sourceNavigatorKey?: string
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* Action creators for the router.
|
||||
*/
|
||||
|
||||
28
src/useChildActionListeners.tsx
Normal file
28
src/useChildActionListeners.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import { ChildActionListener } from './NavigationBuilderContext';
|
||||
|
||||
export default function useChildActionListeners() {
|
||||
const { current: listeners } = React.useRef<ChildActionListener[]>([]);
|
||||
|
||||
const addActionListener = React.useCallback(
|
||||
(listener: ChildActionListener) => {
|
||||
listeners.push(listener);
|
||||
},
|
||||
[listeners]
|
||||
);
|
||||
|
||||
const removeActionListener = React.useCallback(
|
||||
(listener: ChildActionListener) => {
|
||||
const index = listeners.indexOf(listener);
|
||||
|
||||
listeners.splice(index, 1);
|
||||
},
|
||||
[listeners]
|
||||
);
|
||||
|
||||
return {
|
||||
listeners,
|
||||
addActionListener,
|
||||
removeActionListener,
|
||||
};
|
||||
}
|
||||
@@ -9,15 +9,24 @@ import {
|
||||
ScreenProps,
|
||||
} from './types';
|
||||
import SceneView from './SceneView';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
} from './NavigationBuilderContext';
|
||||
|
||||
type Options = {
|
||||
state: NavigationState | PartialState;
|
||||
screens: { [key: string]: ScreenProps<ParamListBase, string> };
|
||||
helpers: NavigationHelpers<ParamListBase>;
|
||||
onAction: (action: NavigationAction) => boolean;
|
||||
onAction: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
addActionListener: (listener: ChildActionListener) => void;
|
||||
removeActionListener: (listener: ChildActionListener) => void;
|
||||
onChildUpdate: (
|
||||
state: NavigationState,
|
||||
focus: boolean,
|
||||
key: string | undefined
|
||||
) => void;
|
||||
};
|
||||
|
||||
const EMPTY_OPTIONS = Object.freeze({});
|
||||
@@ -29,13 +38,19 @@ export default function useDescriptors({
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
addActionListener,
|
||||
removeActionListener,
|
||||
onChildUpdate,
|
||||
}: Options) {
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
helpers,
|
||||
onAction,
|
||||
addActionListener,
|
||||
removeActionListener,
|
||||
onChildUpdate,
|
||||
}),
|
||||
[helpers, onAction]
|
||||
[helpers, onAction, onChildUpdate, addActionListener, removeActionListener]
|
||||
);
|
||||
|
||||
return state.routes.reduce(
|
||||
|
||||
@@ -6,6 +6,8 @@ import useDescriptors from './useDescriptors';
|
||||
import useNavigationHelpers from './useNavigationHelpers';
|
||||
import useOnAction from './useOnAction';
|
||||
import { Router, NavigationState, ScreenProps } from './types';
|
||||
import useOnChildUpdate from './useOnChildUpdate';
|
||||
import useChildActionListeners from './useChildActionListeners';
|
||||
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
@@ -66,6 +68,7 @@ export default function useNavigationBuilder(
|
||||
}),
|
||||
getState: getCurrentState,
|
||||
setState,
|
||||
key,
|
||||
} = React.useContext(NavigationStateContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -92,10 +95,27 @@ export default function useNavigationBuilder(
|
||||
[getCurrentState, router.getRehydratedState, router.getInitialState]
|
||||
);
|
||||
|
||||
const {
|
||||
listeners: actionListeners,
|
||||
addActionListener,
|
||||
removeActionListener,
|
||||
} = useChildActionListeners();
|
||||
|
||||
const onAction = useOnAction({
|
||||
router,
|
||||
getState,
|
||||
setState,
|
||||
key,
|
||||
getStateForAction: router.getStateForAction,
|
||||
actionListeners,
|
||||
});
|
||||
|
||||
const onChildUpdate = useOnChildUpdate({
|
||||
router,
|
||||
onAction,
|
||||
key,
|
||||
getState,
|
||||
setState,
|
||||
});
|
||||
|
||||
const helpers = useNavigationHelpers({
|
||||
@@ -120,6 +140,9 @@ export default function useNavigationBuilder(
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
onChildUpdate,
|
||||
addActionListener,
|
||||
removeActionListener,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from './types';
|
||||
|
||||
type Options = {
|
||||
onAction: (action: NavigationAction) => boolean;
|
||||
onAction: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
actionCreators: ActionCreators;
|
||||
|
||||
@@ -1,32 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import { NavigationAction, NavigationState } from './types';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
} from './NavigationBuilderContext';
|
||||
import { NavigationAction, NavigationState, Router } from './types';
|
||||
|
||||
type Options = {
|
||||
router: Router;
|
||||
getState: () => NavigationState;
|
||||
key?: string;
|
||||
setState: (state: NavigationState) => void;
|
||||
getStateForAction: (
|
||||
state: NavigationState,
|
||||
action: NavigationAction
|
||||
) => NavigationState | null;
|
||||
actionListeners: ChildActionListener[];
|
||||
};
|
||||
|
||||
export default function useOnAction({
|
||||
router,
|
||||
getState,
|
||||
setState,
|
||||
key,
|
||||
getStateForAction,
|
||||
actionListeners,
|
||||
}: Options) {
|
||||
const { onAction: handleActionParent } = React.useContext(
|
||||
NavigationBuilderContext
|
||||
);
|
||||
const {
|
||||
onAction: handleActionParent,
|
||||
onChildUpdate: handleChildUpdateParent,
|
||||
} = React.useContext(NavigationBuilderContext);
|
||||
|
||||
return React.useCallback(
|
||||
(action: NavigationAction) => {
|
||||
(action: NavigationAction, sourceNavigatorKey?: string) => {
|
||||
const state = getState();
|
||||
|
||||
if (sourceNavigatorKey === state.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = getStateForAction(state, action);
|
||||
|
||||
if (result !== null) {
|
||||
if (state !== result) {
|
||||
if (handleChildUpdateParent) {
|
||||
const shouldFocus = router.shouldActionChangeFocus(
|
||||
action,
|
||||
state.key,
|
||||
sourceNavigatorKey
|
||||
);
|
||||
|
||||
handleChildUpdateParent(result, shouldFocus, key);
|
||||
} else if (state !== result) {
|
||||
setState(result);
|
||||
}
|
||||
|
||||
@@ -35,13 +57,38 @@ export default function useOnAction({
|
||||
|
||||
if (handleActionParent !== undefined) {
|
||||
// Bubble action to the parent if the current navigator didn't handle it
|
||||
if (handleActionParent(action)) {
|
||||
if (handleActionParent(action, state.key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
router.shouldActionPropagateToChildren(
|
||||
action,
|
||||
state.key,
|
||||
sourceNavigatorKey
|
||||
)
|
||||
) {
|
||||
for (let i = actionListeners.length - 1; i >= 0; i--) {
|
||||
const listener = actionListeners[i];
|
||||
|
||||
if (listener(action, state.key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[getState, handleActionParent, getStateForAction, setState]
|
||||
[
|
||||
getState,
|
||||
getStateForAction,
|
||||
handleActionParent,
|
||||
router,
|
||||
handleChildUpdateParent,
|
||||
key,
|
||||
setState,
|
||||
actionListeners,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
53
src/useOnChildUpdate.tsx
Normal file
53
src/useOnChildUpdate.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationAction, NavigationState, Router } from './types';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
|
||||
type Options = {
|
||||
router: Router;
|
||||
onAction: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
key?: string;
|
||||
};
|
||||
|
||||
export default function useOnChildUpdate({
|
||||
router,
|
||||
onAction,
|
||||
getState,
|
||||
key: sourceNavigatorKey,
|
||||
setState,
|
||||
}: Options) {
|
||||
const {
|
||||
onChildUpdate: parentOnChildUpdate,
|
||||
addActionListener: parentAddActionListener,
|
||||
removeActionListener: parentRemoveActionListener,
|
||||
} = React.useContext(NavigationBuilderContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
parentAddActionListener && parentAddActionListener(onAction);
|
||||
|
||||
return () => {
|
||||
parentRemoveActionListener && parentRemoveActionListener(onAction);
|
||||
};
|
||||
}, [onAction, parentAddActionListener, parentRemoveActionListener]);
|
||||
|
||||
const onChildUpdate = React.useCallback(
|
||||
(update: NavigationState, focus: boolean, key: string | undefined) => {
|
||||
const state = getState();
|
||||
const result = router.getStateForChildUpdate(state, {
|
||||
update,
|
||||
focus,
|
||||
key,
|
||||
});
|
||||
|
||||
if (parentOnChildUpdate !== undefined) {
|
||||
parentOnChildUpdate(result, focus, sourceNavigatorKey);
|
||||
} else {
|
||||
setState(result);
|
||||
}
|
||||
},
|
||||
[getState, parentOnChildUpdate, sourceNavigatorKey, router, setState]
|
||||
);
|
||||
|
||||
return onChildUpdate;
|
||||
}
|
||||
Reference in New Issue
Block a user