refactor: move navigation.state to a route prop (#6)

This commit is contained in:
Michał Osadnik
2019-07-18 14:11:04 +01:00
committed by satyajit.happy
parent b775dbaacf
commit d3099c18b8
13 changed files with 84 additions and 108 deletions

View File

@@ -262,11 +262,11 @@ const StackRouter: Router<CommonAction | Action> = {
};
export function StackNavigator(props: Props) {
const { navigation, descriptors } = useNavigationBuilder(StackRouter, props);
const { state, descriptors } = useNavigationBuilder(StackRouter, props);
return (
<div style={{ position: 'relative' }}>
{navigation.state.routes.map((route, i) => (
{state.routes.map((route, i) => (
<div
key={route.key}
style={{
@@ -297,10 +297,7 @@ export function StackNavigator(props: Props) {
boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
}}
>
{
descriptors[navigation.state.routes[navigation.state.index].key]
.options.title
}
{descriptors[state.routes[state.index].key].options.title}
</div>
</div>
);

View File

@@ -184,18 +184,18 @@ const TabRouter: Router<Action | CommonAction> = {
};
export function TabNavigator(props: Props) {
const { navigation, descriptors } = useNavigationBuilder(TabRouter, props);
const { state, descriptors } = useNavigationBuilder(TabRouter, props);
return (
<div style={{ display: 'flex', flexDirection: 'row', height: '100%' }}>
{navigation.state.routes.map((route, i, self) => (
{state.routes.map((route, i, self) => (
<div
key={route.key}
style={{
width: `${100 / self.length}%`,
padding: 10,
borderRadius: 3,
backgroundColor: i === navigation.state.index ? 'tomato' : 'white',
backgroundColor: i === state.index ? 'tomato' : 'white',
}}
>
{descriptors[route.key].render()}

View File

@@ -3,8 +3,9 @@ import { render } from 'react-dom';
import {
NavigationContainer,
CompositeNavigationProp,
NavigationHelpers,
PartialState,
NavigationHelpers,
RouteProp,
} from '../src';
import Stack, { StackNavigationProp } from './StackNavigator';
import Tab, { TabNavigationProp } from './TabNavigator';
@@ -26,14 +27,16 @@ const MyTab = Tab<TabParamList>();
const First = ({
navigation,
route,
}: {
navigation: CompositeNavigationProp<
StackNavigationProp<StackParamList, 'first'>,
NavigationHelpers<TabParamList>
>;
route: RouteProp<StackParamList, 'first'>;
}) => (
<div>
<h1>First, {navigation.state.params.author}</h1>
<h1>First, {route.params.author}</h1>
<button type="button" onClick={() => navigation.push('second')}>
Push second
</button>

View File

@@ -7,7 +7,7 @@ export type ChildActionListener = (
) => boolean;
const NavigationBuilderContext = React.createContext<{
helpers?: NavigationHelpers;
navigation?: NavigationHelpers;
onAction?: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
addActionListener?: (listener: ChildActionListener) => void;
removeActionListener?: (listener: ChildActionListener) => void;

View File

@@ -5,20 +5,20 @@ import {
Route,
NavigationState,
NavigationHelpers,
ScreenProps,
RouteConfig,
} from './types';
import EnsureSingleNavigator from './EnsureSingleNavigator';
type Props = {
screen: ScreenProps;
helpers: NavigationHelpers;
screen: RouteConfig;
navigation: NavigationHelpers;
route: Route & { state?: NavigationState };
getState: () => NavigationState;
setState: (state: NavigationState) => void;
};
export default function SceneView(props: Props) {
const { screen, route, helpers, getState, setState } = props;
const { screen, route, navigation: helpers, getState, setState } = props;
const navigation = React.useMemo(
() => ({
@@ -35,9 +35,8 @@ export default function SceneView(props: Props) {
),
});
},
state: route,
}),
[getState, helpers, route, setState]
[getState, helpers, route.key, setState]
);
const getCurrentState = React.useCallback(() => {
@@ -79,11 +78,12 @@ export default function SceneView(props: Props) {
// @ts-ignore
render={screen.component || screen.children}
navigation={navigation}
route={route}
>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={navigation} />
<screen.component navigation={navigation} route={route} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation })
screen.children({ navigation, route })
) : null}
</StaticContainer>
</EnsureSingleNavigator>

View File

@@ -1,6 +1,6 @@
import { ScreenProps } from './types';
import { RouteConfig } from './types';
export default function Screen(_: ScreenProps) {
export default function Screen(_: RouteConfig) {
/* istanbul ignore next */
return null;
}

View File

@@ -93,11 +93,9 @@ beforeEach(() => (MockRouter.key = 0));
it('initializes state for a navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
@@ -148,11 +146,9 @@ it('initializes state for a navigator on navigation', () => {
it('rehydrates state for a navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const BarScreen = (props: any) => {
@@ -195,11 +191,9 @@ it('rehydrates state for a navigator on navigation', () => {
it('initializes state for nested navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = (props: any) => {
@@ -255,11 +249,9 @@ it('initializes state for nested navigator on navigation', () => {
it("doesn't update state if nothing changed", () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
@@ -287,11 +279,9 @@ it("doesn't update state if nothing changed", () => {
it("doesn't update state if action wasn't handled", () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
@@ -319,11 +309,9 @@ it("doesn't update state if action wasn't handled", () => {
it('cleans up state when the navigator unmounts', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
@@ -383,22 +371,15 @@ it("lets parent handle the action if child didn't", () => {
};
const ParentNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(
ParentRouter,
props
);
const { state, descriptors } = useNavigationBuilder(ParentRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const ChildNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = (props: any) => {
@@ -444,11 +425,9 @@ it("lets parent handle the action if child didn't", () => {
it('allows arbitrary state updates by dispatching a function', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
@@ -490,11 +469,9 @@ it('allows arbitrary state updates by dispatching a function', () => {
it('updates route params with setParams', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
let setParams: (params: object) => void = () => undefined;

View File

@@ -37,25 +37,17 @@ it("lets children handle the action if parent didn't", () => {
};
const ChildNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(
ChildRouter,
props
);
const { state, descriptors } = useNavigationBuilder(ChildRouter, props);
return descriptors[
navigation.state.routes[navigation.state.index].key
].render();
return descriptors[state.routes[state.index].key].render();
};
const ParentNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(
ParentRouter,
props
);
const { state, descriptors } = useNavigationBuilder(ParentRouter, props);
return (
<React.Fragment>
{navigation.state.routes.map(route => descriptors[route.key].render())}
{state.routes.map(route => descriptors[route.key].render())}
</React.Fragment>
);
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { ParamListBase, ScreenProps, TypedNavigator } from './types';
import { ParamListBase, RouteConfig, TypedNavigator } from './types';
import Screen from './Screen';
export default function createNavigator<N extends React.ComponentType<any>>(
@@ -12,7 +12,7 @@ export default function createNavigator<N extends React.ComponentType<any>>(
return {
Navigator: RawNavigator,
Screen: Screen as React.ComponentType<
ScreenProps<ParamList, keyof ParamList>
RouteConfig<ParamList, keyof ParamList>
>,
};
};

View File

@@ -204,14 +204,6 @@ export type NavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList
> = NavigationHelpers<ParamList> & {
/**
* State for the child navigator.
*/
state: Omit<Route<RouteName>, 'params'> &
(ParamList[RouteName] extends undefined
? {}
: { params: ParamList[RouteName] });
/**
* Update the param object for the route.
* The new params will be shallow merged with the old one.
@@ -221,6 +213,19 @@ export type NavigationProp<
setParams(params: ParamList[RouteName]): void;
};
export type RouteProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList
> = Omit<Route<RouteName>, 'params'> &
(ParamList[RouteName] extends undefined
? {}
: {
/**
* Params for this route
*/
params: ParamList[RouteName];
});
export type CompositeNavigationProp<
A extends NavigationHelpers<ParamListBase>,
B extends NavigationHelpers<ParamListBase>
@@ -251,7 +256,7 @@ export type Options = {
[key: string]: any;
};
export type ScreenProps<
export type RouteConfig<
ParamList extends ParamListBase = ParamListBase,
RouteName extends keyof ParamList = string
> = {
@@ -295,5 +300,5 @@ export type TypedNavigator<
initialRouteName?: keyof ParamList;
}
>;
Screen: React.ComponentType<ScreenProps<ParamList, keyof ParamList>>;
Screen: React.ComponentType<RouteConfig<ParamList, keyof ParamList>>;
};

View File

@@ -6,7 +6,7 @@ import {
NavigationHelpers,
NavigationState,
ParamListBase,
ScreenProps,
RouteConfig,
} from './types';
import SceneView from './SceneView';
import NavigationBuilderContext, {
@@ -15,8 +15,8 @@ import NavigationBuilderContext, {
type Options = {
state: NavigationState | PartialState;
screens: { [key: string]: ScreenProps<ParamListBase, string> };
helpers: NavigationHelpers<ParamListBase>;
screens: { [key: string]: RouteConfig<ParamListBase, string> };
navigation: NavigationHelpers<ParamListBase>;
onAction: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
getState: () => NavigationState;
setState: (state: NavigationState) => void;
@@ -34,7 +34,7 @@ const EMPTY_OPTIONS = Object.freeze({});
export default function useDescriptors({
state,
screens,
helpers,
navigation,
onAction,
getState,
setState,
@@ -44,13 +44,19 @@ export default function useDescriptors({
}: Options) {
const context = React.useMemo(
() => ({
helpers,
navigation,
onAction,
addActionListener,
removeActionListener,
onChildUpdate,
}),
[helpers, onAction, onChildUpdate, addActionListener, removeActionListener]
[
navigation,
onAction,
onChildUpdate,
addActionListener,
removeActionListener,
]
);
return state.routes.reduce(
@@ -62,7 +68,7 @@ export default function useDescriptors({
return (
<NavigationBuilderContext.Provider value={context}>
<SceneView
helpers={helpers}
navigation={navigation}
route={route}
screen={screen}
getState={getState}

View File

@@ -5,7 +5,7 @@ import useRegisterNavigator from './useRegisterNavigator';
import useDescriptors from './useDescriptors';
import useNavigationHelpers from './useNavigationHelpers';
import useOnAction from './useOnAction';
import { Router, NavigationState, ScreenProps } from './types';
import { Router, NavigationState, RouteConfig } from './types';
import useOnChildUpdate from './useOnChildUpdate';
import useChildActionListeners from './useChildActionListeners';
@@ -29,7 +29,7 @@ export default function useNavigationBuilder(
}
if (React.isValidElement(child) && child.type === Screen) {
return child.props as ScreenProps;
return child.props as RouteConfig;
}
throw new Error(
@@ -42,10 +42,10 @@ export default function useNavigationBuilder(
.filter(Boolean)
.reduce(
(acc, curr) => {
acc[curr!.name] = curr as ScreenProps;
acc[curr!.name] = curr as RouteConfig;
return acc;
},
{} as { [key: string]: ScreenProps }
{} as { [key: string]: RouteConfig }
);
const routeNames = Object.keys(screens);
@@ -140,22 +140,17 @@ export default function useNavigationBuilder(
setState,
});
const helpers = useNavigationHelpers({
const navigation = useNavigationHelpers({
onAction,
getState,
setState,
actionCreators: router.actionCreators,
});
const navigation = React.useMemo(() => ({ ...helpers, state }), [
helpers,
state,
]);
const descriptors = useDescriptors({
state,
screens,
helpers,
navigation,
onAction,
getState,
setState,
@@ -165,6 +160,7 @@ export default function useNavigationBuilder(
});
return {
state,
navigation,
descriptors,
};

View File

@@ -21,7 +21,7 @@ export default function useNavigationHelpers({
setState,
actionCreators,
}: Options) {
const { helpers: parentNavigationHelpers } = React.useContext(
const { navigation: parentNavigationHelpers } = React.useContext(
NavigationBuilderContext
);