diff --git a/packages/core/src/__tests__/index.test.tsx b/packages/core/src/__tests__/index.test.tsx index d9ec1a2b..8ebf9ab8 100644 --- a/packages/core/src/__tests__/index.test.tsx +++ b/packages/core/src/__tests__/index.test.tsx @@ -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 = ( + + + + + + ); + + 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); diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index fc4b9661..37510661 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -377,6 +377,13 @@ export type NavigationProp< dangerouslyGetParent(): | NavigationProp | 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 & PrivateValueStore; diff --git a/packages/core/src/useNavigationCache.tsx b/packages/core/src/useNavigationCache.tsx index a3e7101b..6a3943f7 100644 --- a/packages/core/src/useNavigationCache.tsx +++ b/packages/core/src/useNavigationCache.tsx @@ -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 }), // 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>( (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 => ({