Files
react-navigation/packages/core/src/useDescriptors.tsx
Satyajit Sahoo 162410843c feat: add ability add listeners with listeners prop
This adds ability to listen to events from the component where the navigator is defined, even if the screen is not rendered.

```js
<Tabs.Screen
  name="Chat"
  component={Chat}
  options={{ title: 'Chat' }}
  listeners={{
    tabPress: e => console.log('Tab press', e.target),
  }}
/>
```

Closes #6756
2020-02-26 13:02:22 +01:00

165 lines
4.1 KiB
TypeScript

import * as React from 'react';
import {
NavigationAction,
NavigationState,
ParamListBase,
Router,
} from '@react-navigation/routers';
import SceneView from './SceneView';
import NavigationBuilderContext, {
ChildActionListener,
FocusedNavigationListener,
NavigatorStateGetter,
} from './NavigationBuilderContext';
import { NavigationEventEmitter } from './useEventEmitter';
import useNavigationCache from './useNavigationCache';
import {
Descriptor,
NavigationHelpers,
RouteConfig,
RouteProp,
EventMapBase,
} from './types';
type Options<
State extends NavigationState,
ScreenOptions extends object,
EventMap extends EventMapBase
> = {
state: State;
screens: Record<
string,
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
>;
navigation: NavigationHelpers<ParamListBase>;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
onAction: (
action: NavigationAction,
visitedNavigators?: Set<string>
) => boolean;
getState: () => State;
setState: (state: State) => void;
addActionListener: (listener: ChildActionListener) => void;
addFocusedListener: (listener: FocusedNavigationListener) => void;
addStateGetter: (key: string, getter: NavigatorStateGetter) => void;
onRouteFocus: (key: string) => void;
router: Router<State, NavigationAction>;
emitter: NavigationEventEmitter;
};
/**
* Hook to create descriptor objects for the child routes.
*
* A descriptor object provides 3 things:
* - Helper method to render a screen
* - Options specified by the screen for the navigator
* - Navigation object intended for the route
*/
export default function useDescriptors<
State extends NavigationState,
ScreenOptions extends object,
EventMap extends EventMapBase
>({
state,
screens,
navigation,
screenOptions,
onAction,
getState,
setState,
addActionListener,
addFocusedListener,
addStateGetter,
onRouteFocus,
router,
emitter,
}: Options<State, ScreenOptions, EventMap>) {
const [options, setOptions] = React.useState<Record<string, object>>({});
const { trackAction } = React.useContext(NavigationBuilderContext);
const context = React.useMemo(
() => ({
navigation,
onAction,
addActionListener,
addFocusedListener,
addStateGetter,
onRouteFocus,
trackAction,
}),
[
navigation,
onAction,
addActionListener,
addFocusedListener,
onRouteFocus,
addStateGetter,
trackAction,
]
);
const navigations = useNavigationCache<State, ScreenOptions>({
state,
getState,
navigation,
setOptions,
router,
emitter,
});
return state.routes.reduce(
(acc, route) => {
const screen = screens[route.name];
const navigation = navigations[route.key];
acc[route.key] = {
navigation,
render() {
return (
<NavigationBuilderContext.Provider key={route.key} value={context}>
<SceneView
navigation={navigation}
route={route}
screen={screen}
getState={getState}
setState={setState}
/>
</NavigationBuilderContext.Provider>
);
},
options: {
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
// @ts-ignore
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
},
};
return acc;
},
{} as {
[key: string]: Descriptor<ParamListBase, string, State, ScreenOptions>;
}
);
}