feat: add dangerouslyGetParent (#62)

This commit is contained in:
Michał Osadnik
2019-08-21 12:33:29 +01:00
committed by GitHub
parent 4128654324
commit c0045d82b3
4 changed files with 151 additions and 2 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>;

View File

@@ -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;
},