diff --git a/example/StackNavigator.tsx b/example/StackNavigator.tsx index a1143875..9a9664d1 100644 --- a/example/StackNavigator.tsx +++ b/example/StackNavigator.tsx @@ -80,8 +80,8 @@ const StackRouter: Router = { if (state.routeNames === undefined || state.key === undefined) { state = { ...state, - routeNames: state.routeNames || routeNames, - key: state.key || `stack-${shortid()}`, + routeNames, + key: `stack-${shortid()}`, }; } diff --git a/example/TabNavigator.tsx b/example/TabNavigator.tsx index 9e326577..cdccdb4b 100644 --- a/example/TabNavigator.tsx +++ b/example/TabNavigator.tsx @@ -64,8 +64,8 @@ const TabRouter: Router = { if (state.routeNames === undefined || state.key === undefined) { state = { ...state, - routeNames: state.routeNames || routeNames, - key: state.key || `tab-${shortid()}`, + routeNames, + key: `tab-${shortid()}`, }; } @@ -170,6 +170,7 @@ const TabRouter: Router = { export function TabNavigator(props: Props) { const { navigation, descriptors } = useNavigationBuilder(TabRouter, props); + return (
{navigation.state.routes.map((route, i, self) => ( diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index 428444ad..daaf6da5 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -5,9 +5,9 @@ import NavigationContainer from '../NavigationContainer'; import useNavigationBuilder from '../useNavigationBuilder'; import { Router } from '../types'; -let key = 0; +export const MockRouter: Router<{ type: string }> & { key: number } = { + key: 0, -export const MockRouter: Router<{ type: string }> = { getInitialState({ routeNames, initialRouteName = routeNames[0], @@ -16,7 +16,7 @@ export const MockRouter: Router<{ type: string }> = { const index = routeNames.indexOf(initialRouteName); return { - key: String(key++), + key: String(MockRouter.key++), index, routeNames, routes: routeNames.map(name => ({ @@ -33,8 +33,8 @@ export const MockRouter: Router<{ type: string }> = { if (state.routeNames === undefined || state.key === undefined) { state = { ...state, - routeNames: state.routeNames || routeNames, - key: state.key || String(key++), + routeNames, + key: String(MockRouter.key++), }; } @@ -81,7 +81,7 @@ export const MockRouter: Router<{ type: string }> = { actionCreators: {}, }; -beforeEach(() => (key = 0)); +beforeEach(() => (MockRouter.key = 0)); it('initializes state for a navigator on navigation', () => { const TestNavigator = (props: any) => { @@ -128,7 +128,7 @@ it('initializes state for a navigator on navigation', () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).toBeCalledWith({ index: 0, - key: '1', + key: '0', routeNames: ['foo', 'bar', 'baz'], routes: [ { key: 'foo', name: 'foo', params: { count: 10 } }, @@ -179,7 +179,7 @@ it('rehydrates state for a navigator on navigation', () => { expect(onStateChange).lastCalledWith({ index: 1, - key: '0', + key: '2', routeNames: ['foo', 'bar'], routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }], }); @@ -226,7 +226,7 @@ it('initializes state for nested navigator on navigation', () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).toBeCalledWith({ index: 2, - key: '4', + key: '0', routeNames: ['foo', 'bar', 'baz'], routes: [ { key: 'foo', name: 'foo' }, @@ -236,7 +236,7 @@ it('initializes state for nested navigator on navigation', () => { name: 'baz', state: { index: 0, - key: '3', + key: '1', routeNames: ['qux'], routes: [{ key: 'qux', name: 'qux' }], }, @@ -345,7 +345,7 @@ it('cleans up state when the navigator unmounts', () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).lastCalledWith({ index: 0, - key: '1', + key: '0', routeNames: ['foo', 'bar'], routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }], }); @@ -424,7 +424,7 @@ it("lets parent handle the action if child didn't", () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).lastCalledWith({ index: 2, - key: '4', + key: '0', routeNames: ['foo', 'bar', 'baz'], routes: [ { key: 'baz', name: 'baz' }, @@ -474,7 +474,7 @@ it('allows arbitrary state updates by dispatching a function', () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).toBeCalledWith({ index: 1, - key: '1', + key: '0', routeNames: ['foo', 'bar'], routes: [{ key: 'bar', name: 'bar' }, { key: 'foo', name: 'foo' }], }); @@ -513,7 +513,7 @@ it('updates route params with setParams', () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).lastCalledWith({ index: 0, - key: '2', + key: '0', routeNames: ['foo', 'bar'], routes: [ { key: 'foo', name: 'foo', params: { username: 'alice' } }, @@ -526,7 +526,7 @@ it('updates route params with setParams', () => { expect(onStateChange).toBeCalledTimes(2); expect(onStateChange).lastCalledWith({ index: 0, - key: '2', + key: '0', routeNames: ['foo', 'bar'], routes: [ { key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } }, diff --git a/src/__tests__/useOnChildUpdate.test.tsx b/src/__tests__/useOnChildUpdate.test.tsx index 613b48fa..bfff3e84 100644 --- a/src/__tests__/useOnChildUpdate.test.tsx +++ b/src/__tests__/useOnChildUpdate.test.tsx @@ -6,6 +6,8 @@ import NavigationContainer from '../NavigationContainer'; import Screen from '../Screen'; import { MockRouter } from './index.test'; +beforeEach(() => (MockRouter.key = 0)); + it("lets children handle the action if parent didn't", () => { const ParentRouter: Router<{ type: string }> = { ...MockRouter, @@ -78,9 +80,9 @@ it("lets children handle the action if parent didn't", () => { name: 'baz', state: { index: 0, - key: '3', + key: '4', routeNames: ['qux', 'lex'], - routes: [{ key: 'qux', name: 'qux' },{ key: 'lex', name: 'lex' }], + routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }], }, }, { key: 'bar', name: 'bar' }, @@ -110,7 +112,7 @@ it("lets children handle the action if parent didn't", () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).lastCalledWith({ index: 0, - key: '2', + key: '5', routeNames: ['foo', 'bar', 'baz'], routes: [ { @@ -118,7 +120,7 @@ it("lets children handle the action if parent didn't", () => { name: 'baz', state: { index: 0, - key: '3', + key: '4', routeNames: ['qux', 'lex'], routes: [{ key: 'lex', name: 'lex' }, { key: 'qux', name: 'qux' }], }, diff --git a/src/useNavigationBuilder.tsx b/src/useNavigationBuilder.tsx index 223c9c13..95165c2d 100644 --- a/src/useNavigationBuilder.tsx +++ b/src/useNavigationBuilder.tsx @@ -20,32 +20,30 @@ export default function useNavigationBuilder( ) { useRegisterNavigator(); - const [screens] = React.useState(() => - React.Children.map(options.children, child => { - if (child === null || child === undefined) { - return; - } + const screens = React.Children.map(options.children, child => { + if (child === null || child === undefined) { + return; + } - if (React.isValidElement(child) && child.type === Screen) { - return child.props as ScreenProps; - } + if (React.isValidElement(child) && child.type === Screen) { + return child.props as ScreenProps; + } - throw new Error( - `A navigator can only contain 'Screen' components as its direct children (found '${ - // @ts-ignore - child.type && child.type.name ? child.type.name : String(child) - }')` - ); - }) - .filter(Boolean) - .reduce( - (acc, curr) => { - acc[curr!.name] = curr as ScreenProps; - return acc; - }, - {} as { [key: string]: ScreenProps } - ) - ); + throw new Error( + `A navigator can only contain 'Screen' components as its direct children (found '${ + // @ts-ignore + child.type && child.type.name ? child.type.name : String(child) + }')` + ); + }) + .filter(Boolean) + .reduce( + (acc, curr) => { + acc[curr!.name] = curr as ScreenProps; + return acc; + }, + {} as { [key: string]: ScreenProps } + ); const routeNames = Object.keys(screens); const initialRouteName = @@ -60,17 +58,26 @@ export default function useNavigationBuilder( {} as { [key: string]: object | undefined } ); - const { - state: currentState = router.getInitialState({ + const [initialState] = React.useState(() => + router.getInitialState({ routeNames, initialRouteName, initialParamsList, - }), + }) + ); + + const { + state: currentState = initialState, getState: getCurrentState, setState, key, } = React.useContext(NavigationStateContext); + let state = router.getRehydratedState({ + routeNames, + partialState: currentState, + }); + React.useEffect(() => { return () => { // We need to clean up state for this navigator on unmount @@ -83,13 +90,7 @@ export default function useNavigationBuilder( (): NavigationState => router.getRehydratedState({ routeNames, - partialState: - getCurrentState() || - router.getInitialState({ - routeNames, - initialRouteName, - initialParamsList, - }), + partialState: getCurrentState() || state, }), // eslint-disable-next-line react-hooks/exhaustive-deps [getCurrentState, router.getRehydratedState, router.getInitialState] @@ -125,16 +126,13 @@ export default function useNavigationBuilder( actionCreators: router.actionCreators, }); - const navigation = React.useMemo( - () => ({ - ...helpers, - state: currentState, - }), - [helpers, currentState] - ); + const navigation = React.useMemo(() => ({ ...helpers, state }), [ + helpers, + state, + ]); const descriptors = useDescriptors({ - state: currentState, + state, screens, helpers, onAction,