Skip to content

Commit

Permalink
feat: add canGoBack (facebook#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
osdnk committed Aug 9, 2019
1 parent f3b6d1f commit e9da86e
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 7 deletions.
4 changes: 4 additions & 0 deletions packages/core/src/BaseRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ const BaseRouter = {
shouldActionChangeFocus(action: CommonAction) {
return action.type === 'NAVIGATE';
},

canGoBack() {
return false;
},
};

export default BaseRouter;
4 changes: 1 addition & 3 deletions packages/core/src/NavigationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ const Container = React.forwardRef(function NavigationContainer(

const dispatch = (action: NavigationAction) =>
context.performTransaction(() => {
for (let i = 0; i < actionListeners.length; i++) {
actionListeners[i](action, undefined, null);
}
actionListeners[0](action, undefined, null);
});

React.useImperativeHandle(ref, () => ({
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/__tests__/__fixtures__/MockRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
return false;
},

actionCreators: {},
canGoBack() {
return false;
},
};

return router;
Expand Down
181 changes: 179 additions & 2 deletions packages/core/src/__tests__/useDescriptors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
import MockRouter, {
MockActions,
MockRouterKey,
} from './__fixtures__/MockRouter';
import { DefaultRouterOptions, NavigationState, Router } from '../types';

jest.useFakeTimers();

Expand Down Expand Up @@ -54,6 +57,180 @@ it('sets options with options prop as an object', () => {
`);
});

it("returns correct value for canGoBack when it's not overridden", () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string },
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];

return (
<main>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
};

let result = true;

const TestScreen = ({ navigation }: any): any => {
React.useEffect(() => {
result = navigation.canGoBack();
});

return null;
};

const root = (
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={{ title: 'Hello world' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);

render(root).update(root);

expect(result).toEqual(false);
});

it("returns correct value for canGoBack when it's overridden", () => {
function ParentRouter(options: DefaultRouterOptions) {
const CurrentMockRouter = MockRouter(options);
const ChildRouter: Router<NavigationState, MockActions> = {
...CurrentMockRouter,

canGoBack() {
return true;
},
};
return ChildRouter;
}

const ParentNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string },
any
>(ParentRouter, props);
return descriptors[state.routes[state.index].key].render();
};

const ChildNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string },
any
>(MockRouter, props);

return descriptors[state.routes[state.index].key].render();
};

let result = false;

const TestScreen = ({ navigation }: any): any => {
React.useEffect(() => {
result = navigation.canGoBack();
});

return null;
};

const root = (
<NavigationContainer>
<ParentNavigator>
<Screen name="baz">
{() => (
<ChildNavigator>
<Screen name="qux" component={TestScreen} />
</ChildNavigator>
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
);

render(root).update(root);

expect(result).toEqual(true);
});

it('returns correct value for canGoBack when parent router overrides it', () => {
function OverrodeRouter(options: DefaultRouterOptions) {
const CurrentMockRouter = MockRouter(options);
const ChildRouter: Router<NavigationState, MockActions> = {
...CurrentMockRouter,

canGoBack() {
return true;
},
};
return ChildRouter;
}

const OverrodeNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string },
any
>(OverrodeRouter, props);
return descriptors[state.routes[state.index].key].render();
};

const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
{ title?: string },
any
>(MockRouter, props);

return descriptors[state.routes[state.index].key].render();
};

let result = true;

const TestScreen = ({ navigation }: any): any => {
React.useEffect(() => {
result = navigation.canGoBack();
});

return null;
};

const root = (
<NavigationContainer>
<TestNavigator>
<Screen name="baz">
{() => (
<TestNavigator>
<Screen name="qux" component={TestScreen} />
</TestNavigator>
)}
</Screen>
<Screen name="qux">
{() => (
<OverrodeNavigator>
<Screen name="qux" component={() => null} />
</OverrodeNavigator>
)}
</Screen>
</TestNavigator>
</NavigationContainer>
);

render(root).update(root);

expect(result).toEqual(false);
});

it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ export type Router<
*/
shouldActionChangeFocus(action: NavigationAction): boolean;

/**
* Whether the back action will be handled by navigation
*
* @param state State object to check.
*/
canGoBack(state: State): boolean;

/**
* Action creators for the router.
*/
Expand Down Expand Up @@ -264,6 +271,12 @@ type NavigationHelpersCommon<
* To get notified of focus changes, use `addListener('focus', cb)` and `addListener('blur', cb)`.
*/
isFocused(): boolean;

/**
* Check if dispatching back action will be handled by navigation.
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
*/
canGoBack(): boolean;
} & PrivateValueStore<ParamList, keyof ParamList, {}>;

export type NavigationHelpers<
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/useNavigationBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export default function useNavigationBuilder<
getState,
setState,
emitter,
router,
actionCreators: router.actionCreators,
});

Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/useNavigationHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
NavigationState,
ActionCreators,
ParamListBase,
Router,
} from './types';

type Options<Action extends NavigationAction> = {
Expand All @@ -21,6 +22,7 @@ type Options<Action extends NavigationAction> = {
setState: (state: NavigationState) => void;
actionCreators?: ActionCreators<Action>;
emitter: NavigationEventEmitter;
router: Router<NavigationState, Action>;
};

export default function useNavigationHelpers<Action extends NavigationAction>({
Expand All @@ -29,6 +31,7 @@ export default function useNavigationHelpers<Action extends NavigationAction>({
setState,
actionCreators,
emitter,
router,
}: Options<Action>) {
const parentNavigationHelpers = React.useContext(NavigationContext);
const { performTransaction } = React.useContext(NavigationStateContext);
Expand Down Expand Up @@ -68,14 +71,19 @@ export default function useNavigationHelpers<Action extends NavigationAction>({
isFocused: parentNavigationHelpers
? parentNavigationHelpers.isFocused
: () => true,
canGoBack: () =>
router.canGoBack(getState()) ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
false,
};
}, [
actionCreators,
router,
getState,
parentNavigationHelpers,
emitter.emit,
performTransaction,
setState,
getState,
onAction,
]);
}
4 changes: 4 additions & 0 deletions packages/routers/src/StackRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ export default function StackRouter(options: StackRouterOptions) {
}
},

canGoBack(state) {
return state.routes.length > 1;
},

actionCreators: StackActions,
};

Expand Down

0 comments on commit e9da86e

Please sign in to comment.