feat: add an unhandled action listener (#8895)

Often developers miss these console messages: this allows missed routes to be emitted to whatever event logger users prefer.
This commit is contained in:
ahmadj-levelbenefits
2020-10-09 10:49:20 -04:00
committed by GitHub
parent 90ebfc40b3
commit 80ff5a9c54
3 changed files with 80 additions and 34 deletions

View File

@@ -94,6 +94,7 @@ const BaseNavigationContainer = React.forwardRef(
{
initialState,
onStateChange,
onUnhandledAction,
independent,
children,
}: NavigationContainerProps,
@@ -342,51 +343,56 @@ const BaseNavigationContainer = React.forwardRef(
isFirstMountRef.current = false;
}, [getRootState, emitter, state]);
const onUnhandledAction = React.useCallback((action: NavigationAction) => {
if (process.env.NODE_ENV === 'production') {
return;
}
const defaultOnUnhandledAction = React.useCallback(
(action: NavigationAction) => {
if (process.env.NODE_ENV === 'production') {
return;
}
const payload: Record<string, any> | undefined = action.payload;
const payload: Record<string, any> | undefined = action.payload;
let message = `The action '${action.type}'${
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
} was not handled by any navigator.`;
let message = `The action '${action.type}'${
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
} was not handled by any navigator.`;
switch (action.type) {
case 'NAVIGATE':
case 'PUSH':
case 'REPLACE':
case 'JUMP_TO':
if (payload?.name) {
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
} else {
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
}
switch (action.type) {
case 'NAVIGATE':
case 'PUSH':
case 'REPLACE':
case 'JUMP_TO':
if (payload?.name) {
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
} else {
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
}
break;
case 'GO_BACK':
case 'POP':
case 'POP_TO_TOP':
message += `\n\nIs there any screen to go back to?`;
break;
case 'OPEN_DRAWER':
case 'CLOSE_DRAWER':
case 'TOGGLE_DRAWER':
message += `\n\nIs your screen inside a Drawer navigator?`;
break;
}
break;
case 'GO_BACK':
case 'POP':
case 'POP_TO_TOP':
message += `\n\nIs there any screen to go back to?`;
break;
case 'OPEN_DRAWER':
case 'CLOSE_DRAWER':
case 'TOGGLE_DRAWER':
message += `\n\nIs your screen inside a Drawer navigator?`;
break;
}
message += `\n\nThis is a development-only warning and won't be shown in production.`;
message += `\n\nThis is a development-only warning and won't be shown in production.`;
console.error(message);
}, []);
console.error(message);
},
[]
);
return (
<ScheduleUpdateContext.Provider value={scheduleContext}>
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<UnhandledActionContext.Provider value={onUnhandledAction}>
<UnhandledActionContext.Provider
value={onUnhandledAction ?? defaultOnUnhandledAction}
>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</UnhandledActionContext.Provider>
</NavigationStateContext.Provider>

View File

@@ -721,3 +721,39 @@ it("throws if the ref hasn't finished initializing", () => {
render(element);
});
it('invokes the unhandled action listener with the unhandled action', () => {
const ref = React.createRef<NavigationContainerRef>();
const fn = jest.fn();
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return (
<React.Fragment>
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
const TestScreen = () => <></>;
render(
<BaseNavigationContainer ref={ref} onUnhandledAction={fn}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Screen name="bar" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
act(() => ref.current!.navigate('bar'));
act(() => ref.current!.navigate('baz'));
expect(fn).toHaveBeenCalledWith({
payload: {
name: 'baz',
},
type: 'NAVIGATE',
});
});

View File

@@ -237,6 +237,10 @@ export type NavigationContainerProps = {
* Callback which is called with the latest navigation state when it changes.
*/
onStateChange?: (state: NavigationState | undefined) => void;
/**
* Callback which is called when an action is not handled.
*/
onUnhandledAction?: (action: NavigationAction) => void;
/**
* Whether this navigation container should be independent of parent containers.
* If this is not set to `true`, this container cannot be nested inside another container.