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

Add handling events by navigators not only on the top of the tree #2

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions example/StackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,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()}`,
};
}

Expand Down Expand Up @@ -167,7 +167,6 @@ const StackRouter: Router<CommonAction | Action> = {
],
};
}

return null;

case 'REPLACE': {
Expand Down Expand Up @@ -211,6 +210,35 @@ const StackRouter: Router<CommonAction | Action> = {
}
},

getStateForChildUpdate(state, { update, focus, key }) {
const index = state.routes.findIndex(r => r.key === key);

if (index === -1) {
return state;
}

return {
...state,
index: focus ? index : state.index,
routes: focus
? [
...state.routes.slice(0, index),
{ ...state.routes[index], state: update },
]
: state.routes.map((route, i) =>
i === index ? { ...route, state: update } : route
),
};
},

shouldActionPropagateToChildren(action) {
return action.type === 'NAVIGATE';
},

shouldActionChangeFocus(action) {
return action.type === 'NAVIGATE';
},

actionCreators: {
push(name: string, params?: object) {
return { type: 'PUSH', payload: { name, params } };
Expand Down
28 changes: 26 additions & 2 deletions example/TabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,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()}`,
};
}

Expand Down Expand Up @@ -136,6 +136,30 @@ const TabRouter: Router<Action | CommonAction> = {
}
},

getStateForChildUpdate(state, { update, focus, key }) {
const index = state.routes.findIndex(r => r.key === key);

if (index === -1) {
return state;
}

return {
...state,
index: focus ? index : state.index,
routes: state.routes.map((route, i) =>
i === index ? { ...route, state: update } : route
),
};
},

shouldActionPropagateToChildren(action) {
return action.type === 'NAVIGATE';
},

shouldActionChangeFocus(action) {
return action.type === 'NAVIGATE';
},

actionCreators: {
jumpTo(name: string, params?: object) {
return { type: 'JUMP_TO', payload: { name, params } };
Expand Down
16 changes: 14 additions & 2 deletions src/NavigationBuilderContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import * as React from 'react';
import { NavigationHelpers, NavigationAction } from './types';
import { NavigationHelpers, NavigationAction, NavigationState } from './types';

export type ChildActionListener = (
action: NavigationAction,
sourceRouteKey?: string
) => boolean;

const NavigationBuilderContext = React.createContext<{
helpers?: NavigationHelpers;
onAction?: (action: NavigationAction) => boolean;
onAction?: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
addActionListener?: (listener: ChildActionListener) => void;
removeActionListener?: (listener: ChildActionListener) => void;
onChildUpdate?: (
state: NavigationState,
focus: boolean,
key: string | undefined
) => void;
}>({});

export default NavigationBuilderContext;
1 change: 1 addition & 0 deletions src/NavigationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const NavigationStateContext = React.createContext<{
state?: NavigationState | PartialState;
getState: () => NavigationState | PartialState | undefined;
setState: (state: NavigationState | undefined) => void;
key?: string;
}>({
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
Expand Down
6 changes: 4 additions & 2 deletions src/SceneView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ export default function SceneView(props: Props) {

const getCurrentState = React.useCallback(() => {
const state = getState();
const currentRoute = state.routes.find(r => r.key === route.key);

return state.routes.find(r => r.key === route.key)!.state;
return currentRoute ? currentRoute.state : undefined;
}, [getState, route.key]);

const setCurrentState = React.useCallback(
Expand All @@ -65,8 +66,9 @@ export default function SceneView(props: Props) {
state: route.state,
getState: getCurrentState,
setState: setCurrentState,
key: route.key,
}),
[getCurrentState, route.state, setCurrentState]
[getCurrentState, route.key, route.state, setCurrentState]
);

return (
Expand Down
54 changes: 41 additions & 13 deletions src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import NavigationContainer from '../NavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import { Router } from '../types';

const MockRouter: Router<{ type: string }> = {
export const MockRouter: Router<{ type: string }> & { key: number } = {
key: 0,

getInitialState({
routeNames,
initialRouteName = routeNames[0],
Expand All @@ -14,7 +16,7 @@ const MockRouter: Router<{ type: string }> = {
const index = routeNames.indexOf(initialRouteName);

return {
key: 'root',
key: String(MockRouter.key++),
index,
routeNames,
routes: routeNames.map(name => ({
Expand All @@ -31,8 +33,8 @@ const MockRouter: Router<{ type: string }> = {
if (state.routeNames === undefined || state.key === undefined) {
state = {
...state,
routeNames: state.routeNames || routeNames,
key: state.key || 'root',
routeNames,
key: String(MockRouter.key++),
};
}

Expand All @@ -52,9 +54,35 @@ const MockRouter: Router<{ type: string }> = {
}
},

getStateForChildUpdate(state, { update, focus, key }) {
const index = state.routes.findIndex(r => r.key === key);

if (index === -1) {
return state;
}

return {
...state,
index: focus ? index : state.index,
routes: state.routes.map((route, i) =>
i === index ? { ...route, state: update } : route
),
};
},

shouldActionPropagateToChildren() {
return false;
},

shouldActionChangeFocus() {
return false;
},

actionCreators: {},
};

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

it('initializes state for a navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { navigation, descriptors } = useNavigationBuilder(MockRouter, props);
Expand Down Expand Up @@ -100,7 +128,7 @@ it('initializes state for a navigator on navigation', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
index: 0,
key: 'root',
key: '0',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo', name: 'foo', params: { count: 10 } },
Expand Down Expand Up @@ -151,7 +179,7 @@ it('rehydrates state for a navigator on navigation', () => {

expect(onStateChange).lastCalledWith({
index: 1,
key: 'root',
key: '2',
routeNames: ['foo', 'bar'],
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
});
Expand Down Expand Up @@ -198,7 +226,7 @@ it('initializes state for nested navigator on navigation', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
index: 2,
key: 'root',
key: '0',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo', name: 'foo' },
Expand All @@ -208,7 +236,7 @@ it('initializes state for nested navigator on navigation', () => {
name: 'baz',
state: {
index: 0,
key: 'root',
key: '1',
routeNames: ['qux'],
routes: [{ key: 'qux', name: 'qux' }],
},
Expand Down Expand Up @@ -317,7 +345,7 @@ it('cleans up state when the navigator unmounts', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
index: 0,
key: 'root',
key: '0',
routeNames: ['foo', 'bar'],
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
});
Expand Down Expand Up @@ -396,7 +424,7 @@ it("lets parent handle the action if child didn't", () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
index: 2,
key: 'root',
key: '0',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'baz', name: 'baz' },
Expand Down Expand Up @@ -446,7 +474,7 @@ it('allows arbitrary state updates by dispatching a function', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
index: 1,
key: 'root',
key: '0',
routeNames: ['foo', 'bar'],
routes: [{ key: 'bar', name: 'bar' }, { key: 'foo', name: 'foo' }],
});
Expand Down Expand Up @@ -485,7 +513,7 @@ it('updates route params with setParams', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
index: 0,
key: 'root',
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
Expand All @@ -498,7 +526,7 @@ it('updates route params with setParams', () => {
expect(onStateChange).toBeCalledTimes(2);
expect(onStateChange).lastCalledWith({
index: 0,
key: 'root',
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
Expand Down
Loading