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

Commit

Permalink
feat: add dangerouslyGetParent (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
osdnk authored Aug 21, 2019
1 parent 4128654 commit c0045d8
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 2 deletions.
64 changes: 63 additions & 1 deletion packages/core/src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
74 changes: 74 additions & 0 deletions packages/core/src/__tests__/useNavigation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/useNavigationCache.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -87,6 +91,7 @@ export default function useNavigationCache<
...rest,
...helpers,
...emitter.create(route.key),
dangerouslyGetParent: () => parentNavigation,
dispatch,
setOptions: (options: object) =>
setOptions(o => ({
Expand All @@ -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;
},
Expand Down

0 comments on commit c0045d8

Please sign in to comment.