refactor: mark initial state as stale to determine when to rehydrate (#23)

This commit is contained in:
Satyajit Sahoo
2019-07-22 23:21:56 +02:00
committed by Michał Osadnik
parent 2798c93e8b
commit bd6aa667cb
9 changed files with 71 additions and 19 deletions

View File

@@ -86,9 +86,10 @@ const StackRouter: Router<CommonAction | Action> = {
getRehydratedState({ routeNames, partialState }) {
let state = partialState;
if (state.routeNames === undefined || state.key === undefined) {
if (state.stale) {
state = {
...state,
stale: false,
routeNames,
key: `stack-${shortid()}`,
};

View File

@@ -72,9 +72,10 @@ const TabRouter: Router<Action | CommonAction> = {
getRehydratedState({ routeNames, partialState }) {
let state = partialState;
if (state.routeNames === undefined || state.key === undefined) {
if (state.stale) {
state = {
...state,
stale: false,
routeNames,
key: `tab-${shortid()}`,
};

View File

@@ -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) || '');

View File

@@ -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<State>(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<string> & { state?: PartialState };
}
return { ...route, state: getPartialState(route.state) };
}),
};
};
export default function NavigationContainer({
initialState,
onStateChange,
children,
}: Props) {
const [state, setState] = React.useState<State>(() =>
getPartialState(initialState)
);
const navigationStateRef = React.useRef<State | null>(null);
const isTransactionActiveRef = React.useRef<boolean>(false);
@@ -92,7 +120,7 @@ export default function NavigationContainer(props: Props) {
return (
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{props.children}</EnsureSingleNavigator>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</NavigationStateContext.Provider>
);
}

View File

@@ -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<MockActions> & { key: number } = {
key: 0,
@@ -28,9 +30,10 @@ const MockRouter: Router<MockActions> & { 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++),
};

View File

@@ -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' }));

View File

@@ -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' }],
},

View File

@@ -23,13 +23,26 @@ export type NavigationState = {
/**
* List of rendered routes.
*/
routes: Array<Route<string> & { state?: NavigationState }>;
routes: Array<Route<string> & { state?: NavigationState | PartialState }>;
/**
* Whether the navigation state has been rehydrated.
*/
stale?: false;
};
export type PartialState = Omit<Omit<NavigationState, 'routeNames'>, 'key'> & {
export type InitialState = Omit<
Omit<Omit<NavigationState, 'key'>, 'routes'>,
'routeNames'
> & {
key?: string;
routeNames?: string[];
routes: Array<Route<string> & { state?: InitialState }>;
};
export type PartialState = NavigationState & {
stale: true;
key?: undefined;
routeNames?: undefined;
state?: PartialState;
};
export type Route<RouteName extends string> = {

View File

@@ -61,7 +61,7 @@ export default function useDescriptors<ScreenOptions extends object>({
acc[route.key] = {
render() {
return (
<NavigationBuilderContext.Provider value={context}>
<NavigationBuilderContext.Provider key={route.key} value={context}>
<SceneView
navigation={navigation}
route={route}