feat: add dangerouslyGetState (#63)

This commit is contained in:
Michał Osadnik
2019-08-21 13:00:12 +01:00
committed by GitHub
parent c0045d82b3
commit f7ff0c1cf0
3 changed files with 49 additions and 3 deletions

View File

@@ -4,6 +4,8 @@ import Screen from '../Screen';
import NavigationContainer from '../NavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import useNavigation from '../useNavigation';
import { NavigationState } from '../types';
beforeEach(() => (MockRouterKey.current = 0));
@@ -467,6 +469,7 @@ it('updates route params with setParams applied to parent', () => {
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
{ key: 'bar', name: 'bar' },
],
stale: false,
});
act(() => setParams({ age: 25 }));
@@ -480,6 +483,7 @@ it('updates route params with setParams applied to parent', () => {
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
{ key: 'bar', name: 'bar' },
],
stale: false,
});
});
@@ -519,6 +523,40 @@ it('handles change in route names', () => {
});
});
it('gives access to internal state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
let state: NavigationState | undefined;
const Test = () => {
const navigation = useNavigation();
state = navigation.dangerouslyGetState();
return null;
};
const root = (
<NavigationContainer>
<TestNavigator initialRouteName="bar">
<Screen name="bar" component={Test} />
</TestNavigator>
</NavigationContainer>
);
render(root).update(root);
expect(state).toEqual({
index: 0,
key: '0',
routeNames: ['bar'],
routes: [{ key: 'bar', name: 'bar' }],
stale: false,
});
});
it("throws if navigator doesn't have any screens", () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);

View File

@@ -377,6 +377,13 @@ export type NavigationProp<
dangerouslyGetParent():
| NavigationProp<ParamListBase, string, any, any>
| undefined;
/**
* Returns the navigator's state. Reason why the function is called
* dangerouslyGetState is to discourage developers to use internal navigation's state.
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
*/
dangerouslyGetState(): State;
} & EventConsumer<EventMap & EventMapBase> &
PrivateValueStore<ParamList, RouteName, EventMap>;

View File

@@ -43,10 +43,12 @@ export default function useNavigationCache<
// Cache object which holds navigation objects for each screen
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
// In reality, these deps will rarely change, if ever
const parentNavigation = React.useContext(NavigationContext);
const cache = React.useMemo(
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[getState, navigation, setOptions, router, emitter]
[getState, navigation, setOptions, router, emitter, parentNavigation]
);
const actions = {
@@ -54,8 +56,6 @@ 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];
@@ -92,6 +92,7 @@ export default function useNavigationCache<
...helpers,
...emitter.create(route.key),
dangerouslyGetParent: () => parentNavigation,
dangerouslyGetState: getState as () => State,
dispatch,
setOptions: (options: object) =>
setOptions(o => ({