feat: add canGoBack (#50)

This commit is contained in:
Michał Osadnik
2019-08-09 14:32:31 +01:00
committed by GitHub
parent f3b6d1f18d
commit e9da86e42f
8 changed files with 214 additions and 7 deletions

View File

@@ -78,6 +78,10 @@ const BaseRouter = {
shouldActionChangeFocus(action: CommonAction) {
return action.type === 'NAVIGATE';
},
canGoBack() {
return false;
},
};
export default BaseRouter;

View File

@@ -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, () => ({

View File

@@ -97,7 +97,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
return false;
},
actionCreators: {},
canGoBack() {
return false;
},
};
return router;

View File

@@ -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();
@@ -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<

View File

@@ -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.
*/
@@ -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<

View File

@@ -198,6 +198,7 @@ export default function useNavigationBuilder<
getState,
setState,
emitter,
router,
actionCreators: router.actionCreators,
});

View File

@@ -10,6 +10,7 @@ import {
NavigationState,
ActionCreators,
ParamListBase,
Router,
} from './types';
type Options<Action extends NavigationAction> = {
@@ -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>({
@@ -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);
@@ -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,
]);
}

View File

@@ -225,6 +225,10 @@ export default function StackRouter(options: StackRouterOptions) {
}
},
canGoBack(state) {
return state.routes.length > 1;
},
actionCreators: StackActions,
};