refactor: create initial state only once

This commit is contained in:
satyajit.happy
2019-07-16 15:41:16 +02:00
parent ea8655252d
commit 95773de0a3
5 changed files with 66 additions and 65 deletions

View File

@@ -80,8 +80,8 @@ const StackRouter: Router<CommonAction | Action> = {
if (state.routeNames === undefined || state.key === undefined) {
state = {
...state,
routeNames: state.routeNames || routeNames,
key: state.key || `stack-${shortid()}`,
routeNames,
key: `stack-${shortid()}`,
};
}

View File

@@ -64,8 +64,8 @@ const TabRouter: Router<Action | CommonAction> = {
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<Action | CommonAction> = {
export function TabNavigator(props: Props) {
const { navigation, descriptors } = useNavigationBuilder(TabRouter, props);
return (
<div style={{ display: 'flex', flexDirection: 'row', height: '100%' }}>
{navigation.state.routes.map((route, i, self) => (

View File

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

View File

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

View File

@@ -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,