Skip to content
This repository has been archived by the owner on Feb 8, 2020. It is now read-only.

Commit

Permalink
refactor: let router specify its state shape
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Jul 23, 2019
1 parent 340c324 commit 2873ddb
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 69 deletions.
18 changes: 12 additions & 6 deletions example/StackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import shortid from 'shortid';
import {
useNavigationBuilder,
NavigationProp,
NavigationState,
CommonAction,
ParamListBase,
Router,
Expand Down Expand Up @@ -38,7 +39,12 @@ export type StackNavigationOptions = {
export type StackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<ParamList, RouteName, StackNavigationOptions> & {
> = NavigationProp<
ParamList,
RouteName,
NavigationState,
StackNavigationOptions
> & {
/**
* Push a new screen onto the stack.
*
Expand All @@ -62,7 +68,7 @@ export type StackNavigationProp<
popToTop(): void;
};

const StackRouter: Router<CommonAction | Action> = {
const StackRouter: Router<NavigationState, CommonAction | Action> = {
...BaseRouter,

getInitialState({
Expand Down Expand Up @@ -245,10 +251,10 @@ const StackRouter: Router<CommonAction | Action> = {
};

export function StackNavigator(props: Props) {
const { state, descriptors } = useNavigationBuilder<StackNavigationOptions>(
StackRouter,
props
);
const { state, descriptors } = useNavigationBuilder<
NavigationState,
StackNavigationOptions
>(StackRouter, props);

return (
<div style={{ position: 'relative' }}>
Expand Down
15 changes: 12 additions & 3 deletions example/TabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Router,
createNavigator,
BaseRouter,
NavigationState,
} from '../src/index';

type Props = {
Expand All @@ -32,7 +33,12 @@ export type TabNavigationOptions = {
export type TabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<ParamList, RouteName, TabNavigationOptions> & {
> = NavigationProp<
ParamList,
RouteName,
NavigationState,
TabNavigationOptions
> & {
/**
* Jump to an existing tab.
*
Expand All @@ -46,7 +52,7 @@ export type TabNavigationProp<
): void;
};

const TabRouter: Router<Action | CommonAction> = {
const TabRouter: Router<NavigationState, Action | CommonAction> = {
...BaseRouter,

getInitialState({
Expand Down Expand Up @@ -169,7 +175,10 @@ const TabRouter: Router<Action | CommonAction> = {
};

export function TabNavigator(props: Props) {
const { state, descriptors } = useNavigationBuilder(TabRouter, props);
const { state, descriptors } = useNavigationBuilder<
NavigationState,
TabNavigationOptions
>(TabRouter, props);

return (
<div style={{ display: 'flex', flexDirection: 'row', height: '100%' }}>
Expand Down
8 changes: 5 additions & 3 deletions src/BaseActions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PartialState, TargetRoute } from './types';
import { PartialState, NavigationState, TargetRoute } from './types';

export type Action =
| { type: 'GO_BACK' }
Expand All @@ -14,7 +14,7 @@ export type Action =
}
| {
type: 'RESET';
payload: PartialState & { key?: string };
payload: PartialState<NavigationState> & { key?: string };
}
| {
type: 'SET_PARAMS';
Expand Down Expand Up @@ -47,7 +47,9 @@ export function replace(name: string, params?: object): Action {
return { type: 'REPLACE', payload: { name, params } };
}

export function reset(state: PartialState & { key?: string }): Action {
export function reset(
state: PartialState<NavigationState> & { key?: string }
): Action {
return { type: 'RESET', payload: state };
}

Expand Down
7 changes: 5 additions & 2 deletions src/BaseRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import shortid from 'shortid';
import { CommonAction, NavigationState } from './types';

const BaseRouter = {
getStateForAction(state: NavigationState, action: CommonAction) {
getStateForAction<State extends NavigationState>(
state: State,
action: CommonAction
): State | null {
switch (action.type) {
case 'REPLACE': {
return {
Expand Down Expand Up @@ -35,7 +38,7 @@ const BaseRouter = {
action.payload.key === state.key
) {
return {
...action.payload,
...(action.payload as any),
key: state.key,
routeNames: state.routeNames,
};
Expand Down
16 changes: 10 additions & 6 deletions src/NavigationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ import { Route, NavigationState, InitialState, PartialState } from './types';

type Props = {
initialState?: InitialState;
onStateChange?: (state: NavigationState | PartialState | undefined) => void;
onStateChange?: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
children: React.ReactNode;
};

type State = NavigationState | PartialState | undefined;
type State = NavigationState | PartialState<NavigationState> | undefined;

const MISSING_CONTEXT_ERROR =
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";

export const NavigationStateContext = React.createContext<{
state?: NavigationState | PartialState;
getState: () => NavigationState | PartialState | undefined;
state?: NavigationState | PartialState<NavigationState>;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (state: NavigationState | undefined) => void;
key?: string;
performTransaction: (action: () => void) => void;
Expand All @@ -33,7 +35,7 @@ export const NavigationStateContext = React.createContext<{

const getPartialState = (
state: InitialState | undefined
): PartialState | undefined => {
): PartialState<NavigationState> | undefined => {
if (state === undefined) {
return;
}
Expand All @@ -46,7 +48,9 @@ const getPartialState = (
routeNames: undefined,
routes: state.routes.map(route => {
if (route.state === undefined) {
return route as Route<string> & { state?: PartialState };
return route as Route<string> & {
state?: PartialState<NavigationState>;
};
}

return { ...route, state: getPartialState(route.state) };
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/__fixtures__/MockRouter.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Router, CommonAction } from '../../types';
import { Router, CommonAction, NavigationState } from '../../types';
import { BaseRouter } from '../../index';

export type MockActions = CommonAction & {
type: 'NOOP' | 'REVERSE' | 'UPDATE';
};

const MockRouter: Router<MockActions> & { key: number } = {
const MockRouter: Router<NavigationState, MockActions> & { key: number } = {
key: 0,

getInitialState({
Expand Down
33 changes: 20 additions & 13 deletions src/__tests__/useDescriptors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { NavigationState } from '../types';

jest.useFakeTimers();

beforeEach(() => (MockRouter.key = 0));

it('sets options with options prop as an object', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<{ title?: string }>(
MockRouter,
props
);
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string }
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];

return (
Expand Down Expand Up @@ -54,10 +55,10 @@ it('sets options with options prop as an object', () => {

it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<{ title?: string }>(
MockRouter,
props
);
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string }
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];

return (
Expand Down Expand Up @@ -98,10 +99,13 @@ it('sets options with options prop as a fuction', () => {

it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<{
title?: string;
color?: string;
}>(MockRouter, props);
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{
title?: string;
color?: string;
}
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];

return (
Expand Down Expand Up @@ -147,7 +151,10 @@ it('sets initial options with setOptions', () => {

it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<any>(MockRouter, props);
const { state, descriptors } = useNavigationBuilder<NavigationState, any>(
MockRouter,
props
);
const { render, options } = descriptors[state.routes[state.index].key];

return (
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/useOnAction.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import { Router } from '../types';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import { Router, NavigationState } from '../types';

beforeEach(() => (MockRouter.key = 0));

it("lets parent handle the action if child didn't", () => {
const ParentRouter: Router<MockActions> = {
const ParentRouter: Router<NavigationState, MockActions> = {
...MockRouter,

getStateForAction(state, action) {
Expand Down Expand Up @@ -78,15 +78,15 @@ it("lets parent handle the action if child didn't", () => {
});

it("lets children handle the action if parent didn't", () => {
const ParentRouter: Router<MockActions> = {
const ParentRouter: Router<NavigationState, MockActions> = {
...MockRouter,

shouldActionPropagateToChildren() {
return true;
},
};

const ChildRouter: Router<MockActions> = {
const ChildRouter: Router<NavigationState, MockActions> = {
...MockRouter,

shouldActionChangeFocus() {
Expand Down

0 comments on commit 2873ddb

Please sign in to comment.