Skip to content
This repository was archived by the owner on Feb 8, 2020. It is now read-only.

Commit c0045d8

Browse files
authored
feat: add dangerouslyGetParent (#62)
1 parent 4128654 commit c0045d8

File tree

4 files changed

+151
-2
lines changed

4 files changed

+151
-2
lines changed

packages/core/src/__tests__/index.test.tsx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,68 @@ it('updates route params with setParams', () => {
421421
});
422422
});
423423

424+
it('updates route params with setParams applied to parent', () => {
425+
const TestNavigator = (props: any) => {
426+
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
427+
428+
return descriptors[state.routes[state.index].key].render();
429+
};
430+
431+
let setParams: (params: object) => void = () => undefined;
432+
433+
const FooScreen = (props: any) => {
434+
const parent = props.navigation.dangerouslyGetParent();
435+
if (parent) {
436+
setParams = parent.setParams;
437+
}
438+
439+
return null;
440+
};
441+
442+
const onStateChange = jest.fn();
443+
444+
render(
445+
<NavigationContainer onStateChange={onStateChange}>
446+
<TestNavigator initialRouteName="foo">
447+
<Screen name="foo">
448+
{() => (
449+
<TestNavigator initialRouteName="baz">
450+
<Screen name="baz" component={FooScreen} />
451+
</TestNavigator>
452+
)}
453+
</Screen>
454+
<Screen name="bar" component={jest.fn()} />
455+
</TestNavigator>
456+
</NavigationContainer>
457+
);
458+
459+
act(() => setParams({ username: 'alice' }));
460+
461+
expect(onStateChange).toBeCalledTimes(1);
462+
expect(onStateChange).lastCalledWith({
463+
index: 0,
464+
key: '0',
465+
routeNames: ['foo', 'bar'],
466+
routes: [
467+
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
468+
{ key: 'bar', name: 'bar' },
469+
],
470+
});
471+
472+
act(() => setParams({ age: 25 }));
473+
474+
expect(onStateChange).toBeCalledTimes(2);
475+
expect(onStateChange).lastCalledWith({
476+
index: 0,
477+
key: '0',
478+
routeNames: ['foo', 'bar'],
479+
routes: [
480+
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
481+
{ key: 'bar', name: 'bar' },
482+
],
483+
});
484+
});
485+
424486
it('handles change in route names', () => {
425487
const TestNavigator = (props: any): any => {
426488
useNavigationBuilder(MockRouter, props);
@@ -491,7 +553,7 @@ it('throws if navigator is not inside a container', () => {
491553
);
492554
});
493555

494-
it('throws if muliple navigators rendered under one container', () => {
556+
it('throws if multiple navigators rendered under one container', () => {
495557
const TestNavigator = (props: any) => {
496558
useNavigationBuilder(MockRouter, props);
497559
return null;

packages/core/src/__tests__/useNavigation.test.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,80 @@ it('gets navigation prop from context', () => {
3232
);
3333
});
3434

35+
it("gets navigation's parent from context", () => {
36+
expect.assertions(1);
37+
38+
const TestNavigator = (props: any): any => {
39+
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
40+
41+
return state.routes.map(route => descriptors[route.key].render());
42+
};
43+
44+
const Test = () => {
45+
const navigation = useNavigation();
46+
47+
expect(navigation.dangerouslyGetParent()).toBeDefined();
48+
49+
return null;
50+
};
51+
52+
render(
53+
<NavigationContainer>
54+
<TestNavigator>
55+
<Screen name="foo">
56+
{() => (
57+
<TestNavigator>
58+
<Screen name="bar" component={Test} />
59+
</TestNavigator>
60+
)}
61+
</Screen>
62+
</TestNavigator>
63+
</NavigationContainer>
64+
);
65+
});
66+
67+
it("gets navigation's parent's parent from context", () => {
68+
expect.assertions(2);
69+
70+
const TestNavigator = (props: any): any => {
71+
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
72+
73+
return state.routes.map(route => descriptors[route.key].render());
74+
};
75+
76+
const Test = () => {
77+
const navigation = useNavigation();
78+
const parent = navigation.dangerouslyGetParent();
79+
80+
expect(parent).toBeDefined();
81+
if (parent !== undefined) {
82+
expect(parent.navigate).toBeDefined();
83+
}
84+
85+
return null;
86+
};
87+
88+
render(
89+
<NavigationContainer>
90+
<TestNavigator>
91+
<Screen name="foo">
92+
{() => (
93+
<TestNavigator>
94+
<Screen name="foo">
95+
{() => (
96+
<TestNavigator>
97+
<Screen name="quo" component={Test} />
98+
</TestNavigator>
99+
)}
100+
</Screen>
101+
</TestNavigator>
102+
)}
103+
</Screen>
104+
</TestNavigator>
105+
</NavigationContainer>
106+
);
107+
});
108+
35109
it('throws if called outside a navigation context', () => {
36110
expect.assertions(1);
37111

packages/core/src/types.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@ export type NavigationProp<
369369
* It can be useful to decide whether to display a back button in a stack.
370370
*/
371371
isFirstRouteInParent(): boolean;
372+
/**
373+
* Returns the parent navigator, if any. Reason why the function is called
374+
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
375+
* of parent and other hard-to-follow patterns.
376+
*/
377+
dangerouslyGetParent():
378+
| NavigationProp<ParamListBase, string, any, any>
379+
| undefined;
372380
} & EventConsumer<EventMap & EventMapBase> &
373381
PrivateValueStore<ParamList, RouteName, EventMap>;
374382

packages/core/src/useNavigationCache.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react';
22
import * as BaseActions from './BaseActions';
33
import { NavigationEventEmitter } from './useEventEmitter';
4+
import NavigationContext from './NavigationContext';
5+
46
import {
57
NavigationAction,
68
NavigationHelpers,
@@ -52,6 +54,8 @@ export default function useNavigationCache<
5254
...BaseActions,
5355
};
5456

57+
const parentNavigation = React.useContext(NavigationContext);
58+
5559
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
5660
(acc, route, index) => {
5761
const previous = cache.current[route.key];
@@ -87,6 +91,7 @@ export default function useNavigationCache<
8791
...rest,
8892
...helpers,
8993
...emitter.create(route.key),
94+
dangerouslyGetParent: () => parentNavigation,
9095
dispatch,
9196
setOptions: (options: object) =>
9297
setOptions(o => ({
@@ -100,7 +105,7 @@ export default function useNavigationCache<
100105
return false;
101106
}
102107

103-
// If the current screen is focused, we also need to check if parent navigtor is focused
108+
// If the current screen is focused, we also need to check if parent navigator is focused
104109
// This makes sure that we return the focus state in the whole tree, not just this navigator
105110
return navigation ? navigation.isFocused() : true;
106111
},

0 commit comments

Comments
 (0)