import type { NavigationAction, NavigationState, ParamListBase, Router, } from '@react-navigation/routers'; import * as React from 'react'; import NavigationBuilderContext, { AddKeyedListener, AddListener, } from './NavigationBuilderContext'; import NavigationContext from './NavigationContext'; import NavigationRouteContext from './NavigationRouteContext'; import SceneView from './SceneView'; import type { Descriptor, EventMapBase, NavigationHelpers, NavigationProp, RouteConfig, RouteProp, } from './types'; import type { NavigationEventEmitter } from './useEventEmitter'; import useNavigationCache from './useNavigationCache'; import useRouteCache from './useRouteCache'; export type ScreenConfigWithParent< State extends NavigationState, ScreenOptions extends {}, EventMap extends EventMapBase > = { keys: (string | undefined)[]; options: (ScreenOptionsOrCallback | undefined)[] | undefined; props: RouteConfig; }; type ScreenOptionsOrCallback = | ScreenOptions | ((props: { route: RouteProp; navigation: any; }) => ScreenOptions); type Options< State extends NavigationState, ScreenOptions extends {}, EventMap extends EventMapBase > = { state: State; screens: Record< string, ScreenConfigWithParent >; navigation: NavigationHelpers; screenOptions?: ScreenOptionsOrCallback; defaultScreenOptions?: | ScreenOptions | ((props: { route: RouteProp; navigation: any; options: ScreenOptions; }) => ScreenOptions); onAction: (action: NavigationAction) => boolean; getState: () => State; setState: (state: State) => void; addListener: AddListener; addKeyedListener: AddKeyedListener; onRouteFocus: (key: string) => void; router: Router; 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, ActionHelpers extends Record void>, ScreenOptions extends {}, EventMap extends EventMapBase >({ state, screens, navigation, screenOptions, defaultScreenOptions, onAction, getState, setState, addListener, addKeyedListener, onRouteFocus, router, emitter, }: Options) { const [options, setOptions] = React.useState>({}); const { onDispatchAction, onOptionsChange, stackRef } = React.useContext( NavigationBuilderContext ); const context = React.useMemo( () => ({ navigation, onAction, addListener, addKeyedListener, onRouteFocus, onDispatchAction, onOptionsChange, stackRef, }), [ navigation, onAction, addListener, addKeyedListener, onRouteFocus, onDispatchAction, onOptionsChange, stackRef, ] ); const navigations = useNavigationCache({ state, getState, navigation, setOptions, router, emitter, }); const routes = useRouteCache(state.routes); return routes.reduce< Record< string, Descriptor< ScreenOptions, NavigationProp< ParamListBase, string, string | undefined, State, ScreenOptions, EventMap > & ActionHelpers, RouteProp > > >((acc, route, i) => { const config = screens[route.name]; const screen = config.props; const navigation = navigations[route.key]; const optionsList = [ // The default `screenOptions` passed to the navigator screenOptions, // The `screenOptions` props passed to `Group` elements ...((config.options ? config.options.filter(Boolean) : []) as ScreenOptionsOrCallback[]), // The `options` prop passed to `Screen` elements, screen.options, // The options set via `navigation.setOptions` options[route.key], ]; const customOptions = optionsList.reduce( (acc, curr) => Object.assign( acc, typeof curr !== 'function' ? curr : curr({ route, navigation }) ), {} as ScreenOptions ); const mergedOptions = { ...(typeof defaultScreenOptions === 'function' ? // @ts-expect-error: ts gives incorrect error here defaultScreenOptions({ route, navigation, options: customOptions, }) : defaultScreenOptions), ...customOptions, }; const clearOptions = () => setOptions((o) => { if (route.key in o) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { [route.key]: _, ...rest } = o; return rest; } return o; }); acc[route.key] = { route, // @ts-expect-error: it's missing action helpers, fix later navigation, render() { return ( ); }, options: mergedOptions as ScreenOptions, }; return acc; }, {}); }