From bd6aa667cb33db01d5dfdd86b4bf181207ded77b Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Mon, 22 Jul 2019 23:21:56 +0200 Subject: [PATCH] refactor: mark initial state as stale to determine when to rehydrate (#23) --- example/StackNavigator.tsx | 3 +- example/TabNavigator.tsx | 3 +- example/index.tsx | 4 +-- src/NavigationContainer.tsx | 40 +++++++++++++++++++---- src/__tests__/__fixtures__/MockRouter.tsx | 7 ++-- src/__tests__/index.test.tsx | 6 +++- src/__tests__/useOnAction.test.tsx | 6 ++-- src/types.tsx | 19 +++++++++-- src/useDescriptors.tsx | 2 +- 9 files changed, 71 insertions(+), 19 deletions(-) diff --git a/example/StackNavigator.tsx b/example/StackNavigator.tsx index f2fa5a1d..1adda2bb 100644 --- a/example/StackNavigator.tsx +++ b/example/StackNavigator.tsx @@ -86,9 +86,10 @@ const StackRouter: Router = { getRehydratedState({ routeNames, partialState }) { let state = partialState; - if (state.routeNames === undefined || state.key === undefined) { + if (state.stale) { state = { ...state, + stale: false, routeNames, key: `stack-${shortid()}`, }; diff --git a/example/TabNavigator.tsx b/example/TabNavigator.tsx index 23afae58..c78681f5 100644 --- a/example/TabNavigator.tsx +++ b/example/TabNavigator.tsx @@ -72,9 +72,10 @@ const TabRouter: Router = { getRehydratedState({ routeNames, partialState }) { let state = partialState; - if (state.routeNames === undefined || state.key === undefined) { + if (state.stale) { state = { ...state, + stale: false, routeNames, key: `tab-${shortid()}`, }; diff --git a/example/index.tsx b/example/index.tsx index 4eec30f6..00b62459 100644 --- a/example/index.tsx +++ b/example/index.tsx @@ -3,9 +3,9 @@ import { render } from 'react-dom'; import { NavigationContainer, CompositeNavigationProp, - PartialState, NavigationProp, RouteProp, + InitialState, } from '../src'; import StackNavigator, { StackNavigationProp } from './StackNavigator'; import TabNavigator, { TabNavigationProp } from './TabNavigator'; @@ -143,7 +143,7 @@ const Fifth = ({ const PERSISTENCE_KEY = 'NAVIGATION_STATE'; -let initialState: PartialState | undefined; +let initialState: InitialState | undefined; try { initialState = JSON.parse(localStorage.getItem(PERSISTENCE_KEY) || ''); diff --git a/src/NavigationContainer.tsx b/src/NavigationContainer.tsx index d6c671ef..c87ac5dc 100644 --- a/src/NavigationContainer.tsx +++ b/src/NavigationContainer.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import EnsureSingleNavigator from './EnsureSingleNavigator'; -import { NavigationState, PartialState } from './types'; +import { Route, NavigationState, InitialState, PartialState } from './types'; type Props = { - initialState?: PartialState; + initialState?: InitialState; onStateChange?: (state: NavigationState | PartialState | undefined) => void; children: React.ReactNode; }; @@ -31,9 +31,37 @@ export const NavigationStateContext = React.createContext<{ }, }); -export default function NavigationContainer(props: Props) { - const { onStateChange } = props; - const [state, setState] = React.useState(props.initialState); +const getPartialState = ( + state: InitialState | undefined +): PartialState | undefined => { + if (state === undefined) { + return; + } + + // @ts-ignore + return { + ...state, + stale: true, + key: undefined, + routeNames: undefined, + routes: state.routes.map(route => { + if (route.state === undefined) { + return route as Route & { state?: PartialState }; + } + + return { ...route, state: getPartialState(route.state) }; + }), + }; +}; + +export default function NavigationContainer({ + initialState, + onStateChange, + children, +}: Props) { + const [state, setState] = React.useState(() => + getPartialState(initialState) + ); const navigationStateRef = React.useRef(null); const isTransactionActiveRef = React.useRef(false); @@ -92,7 +120,7 @@ export default function NavigationContainer(props: Props) { return ( - {props.children} + {children} ); } diff --git a/src/__tests__/__fixtures__/MockRouter.tsx b/src/__tests__/__fixtures__/MockRouter.tsx index 69c1f77d..aa14680a 100644 --- a/src/__tests__/__fixtures__/MockRouter.tsx +++ b/src/__tests__/__fixtures__/MockRouter.tsx @@ -1,7 +1,9 @@ import { Router, CommonAction } from '../../types'; import { BaseRouter } from '../../index'; -export type MockActions = CommonAction & { type: 'NOOP' | 'REVERSE' | 'UPDATE' }; +export type MockActions = CommonAction & { + type: 'NOOP' | 'REVERSE' | 'UPDATE'; +}; const MockRouter: Router & { key: number } = { key: 0, @@ -28,9 +30,10 @@ const MockRouter: Router & { key: number } = { getRehydratedState({ routeNames, partialState }) { let state = partialState; - if (state.routeNames === undefined || state.key === undefined) { + if (state.stale) { state = { ...state, + stale: false, routeNames, key: String(MockRouter.key++), }; diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index d594f2ba..4959742e 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -102,6 +102,7 @@ it('rehydrates state for a navigator on navigation', () => { key: '2', routeNames: ['foo', 'bar'], routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }], + stale: false, }); }); @@ -445,7 +446,10 @@ it('updates another route params with setParams', () => { index: 0, key: '0', routeNames: ['foo', 'bar'], - routes: [{ key: 'foo', name: 'foo', params: undefined }, { key: 'bar', name: 'bar', params: { username: 'alice' } }], + routes: [ + { key: 'foo', name: 'foo', params: undefined }, + { key: 'bar', name: 'bar', params: { username: 'alice' } }, + ], }); act(() => setParams({ age: 25 }, { name: 'bar' })); diff --git a/src/__tests__/useOnAction.test.tsx b/src/__tests__/useOnAction.test.tsx index d62faaf7..243e751e 100644 --- a/src/__tests__/useOnAction.test.tsx +++ b/src/__tests__/useOnAction.test.tsx @@ -174,16 +174,18 @@ it("lets children handle the action if parent didn't", () => { expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).lastCalledWith({ + stale: false, index: 0, - key: '5', + key: '7', routeNames: ['foo', 'bar', 'baz'], routes: [ { key: 'baz', name: 'baz', state: { + stale: false, index: 0, - key: '4', + key: '6', routeNames: ['qux', 'lex'], routes: [{ key: 'lex', name: 'lex' }, { key: 'qux', name: 'qux' }], }, diff --git a/src/types.tsx b/src/types.tsx index 44f5c399..0b27813d 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -23,13 +23,26 @@ export type NavigationState = { /** * List of rendered routes. */ - routes: Array & { state?: NavigationState }>; + routes: Array & { state?: NavigationState | PartialState }>; + /** + * Whether the navigation state has been rehydrated. + */ + stale?: false; }; -export type PartialState = Omit, 'key'> & { +export type InitialState = Omit< + Omit, 'routes'>, + 'routeNames' +> & { + key?: string; + routeNames?: string[]; + routes: Array & { state?: InitialState }>; +}; + +export type PartialState = NavigationState & { + stale: true; key?: undefined; routeNames?: undefined; - state?: PartialState; }; export type Route = { diff --git a/src/useDescriptors.tsx b/src/useDescriptors.tsx index d5edf625..34017056 100644 --- a/src/useDescriptors.tsx +++ b/src/useDescriptors.tsx @@ -61,7 +61,7 @@ export default function useDescriptors({ acc[route.key] = { render() { return ( - +