mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-12 22:51:18 +08:00
feat: add dangerouslyGetParent (#62)
This commit is contained in:
@@ -421,6 +421,68 @@ it('updates route params with setParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('updates route params with setParams applied to parent', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
let setParams: (params: object) => void = () => undefined;
|
||||
|
||||
const FooScreen = (props: any) => {
|
||||
const parent = props.navigation.dangerouslyGetParent();
|
||||
if (parent) {
|
||||
setParams = parent.setParams;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
render(
|
||||
<NavigationContainer onStateChange={onStateChange}>
|
||||
<TestNavigator initialRouteName="foo">
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator initialRouteName="baz">
|
||||
<Screen name="baz" component={FooScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
act(() => setParams({ username: 'alice' }));
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
});
|
||||
|
||||
act(() => setParams({ age: 25 }));
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(2);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('handles change in route names', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
@@ -491,7 +553,7 @@ it('throws if navigator is not inside a container', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if muliple navigators rendered under one container', () => {
|
||||
it('throws if multiple navigators rendered under one container', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
return null;
|
||||
|
||||
@@ -32,6 +32,80 @@ it('gets navigation prop from context', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("gets navigation's parent from context", () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
expect(navigation.dangerouslyGetParent()).toBeDefined();
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="bar" component={Test} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
});
|
||||
|
||||
it("gets navigation's parent's parent from context", () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
if (parent !== undefined) {
|
||||
expect(parent.navigate).toBeDefined();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="quo" component={Test} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if called outside a navigation context', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
|
||||
@@ -369,6 +369,14 @@ export type NavigationProp<
|
||||
* It can be useful to decide whether to display a back button in a stack.
|
||||
*/
|
||||
isFirstRouteInParent(): boolean;
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
*/
|
||||
dangerouslyGetParent():
|
||||
| NavigationProp<ParamListBase, string, any, any>
|
||||
| undefined;
|
||||
} & EventConsumer<EventMap & EventMapBase> &
|
||||
PrivateValueStore<ParamList, RouteName, EventMap>;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import * as BaseActions from './BaseActions';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationHelpers,
|
||||
@@ -52,6 +54,8 @@ export default function useNavigationCache<
|
||||
...BaseActions,
|
||||
};
|
||||
|
||||
const parentNavigation = React.useContext(NavigationContext);
|
||||
|
||||
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
||||
(acc, route, index) => {
|
||||
const previous = cache.current[route.key];
|
||||
@@ -87,6 +91,7 @@ export default function useNavigationCache<
|
||||
...rest,
|
||||
...helpers,
|
||||
...emitter.create(route.key),
|
||||
dangerouslyGetParent: () => parentNavigation,
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions(o => ({
|
||||
@@ -100,7 +105,7 @@ export default function useNavigationCache<
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the current screen is focused, we also need to check if parent navigtor is focused
|
||||
// If the current screen is focused, we also need to check if parent navigator is focused
|
||||
// This makes sure that we return the focus state in the whole tree, not just this navigator
|
||||
return navigation ? navigation.isFocused() : true;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user