import type { NavigationState, ParamListBase, PartialState, Route, } from '@react-navigation/routers'; import * as React from 'react'; import { EnsureSingleNavigator } from './EnsureSingleNavigator'; import { type FocusedRouteState, NavigationFocusedRouteStateContext, } from './NavigationFocusedRouteStateContext'; import { NavigationStateContext } from './NavigationStateContext'; import { StaticContainer } from './StaticContainer'; import type { NavigationProp, RouteConfigComponent } from './types'; import { useOptionsGetters } from './useOptionsGetters'; type Props = { screen: RouteConfigComponent & { name: string }; navigation: NavigationProp< ParamListBase, string, string | undefined, State, ScreenOptions >; route: Route; routeState: NavigationState | PartialState | undefined; getState: () => State; setState: (state: State) => void; options: object; clearOptions: () => void; }; /** * Component which takes care of rendering the screen for a route. * It provides all required contexts and applies optimizations when applicable. */ export function SceneView< State extends NavigationState, ScreenOptions extends {}, >({ screen, route, navigation, routeState, getState, setState, options, clearOptions, }: Props) { const navigatorKeyRef = React.useRef(undefined); const getKey = React.useCallback(() => navigatorKeyRef.current, []); const { addOptionsGetter } = useOptionsGetters({ key: route.key, options, navigation, }); const setKey = React.useCallback((key: string) => { navigatorKeyRef.current = key; }, []); const getCurrentState = React.useCallback(() => { const state = getState(); const currentRoute = state.routes.find((r) => r.key === route.key); return currentRoute ? currentRoute.state : undefined; }, [getState, route.key]); const setCurrentState = React.useCallback( (child: NavigationState | PartialState | undefined) => { const state = getState(); setState({ ...state, routes: state.routes.map((r) => { if (r.key !== route.key) { return r; } const nextRoute = { ...r, state: child }; // Before updating the state, cleanup any nested screen and state // This will avoid the navigator trying to handle them again if ( nextRoute.params && (('state' in nextRoute.params && typeof nextRoute.params.state === 'object' && nextRoute.params.state !== null) || ('screen' in nextRoute.params && typeof nextRoute.params.screen === 'string')) ) { // @ts-expect-error: we don't have correct type for params // eslint-disable-next-line @typescript-eslint/no-unused-vars const { state, screen, params, initial, ...rest } = nextRoute.params; if (Object.keys(rest).length) { nextRoute.params = rest; } else { delete nextRoute.params; } } return nextRoute; }), }); }, [getState, route.key, setState] ); const isInitialRef = React.useRef(true); React.useEffect(() => { isInitialRef.current = false; }); // Clear options set by this screen when it is unmounted React.useEffect(() => { return clearOptions; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const getIsInitial = React.useCallback(() => isInitialRef.current, []); const parentFocusedRouteState = React.useContext( NavigationFocusedRouteStateContext ); const focusedRouteState = React.useMemo(() => { const state: FocusedRouteState = { routes: [ { key: route.key, name: route.name, params: route.params, path: route.path, }, ], }; // Add our state to the innermost route of the parent state const addState = ( parent: FocusedRouteState | undefined ): FocusedRouteState => { const parentRoute = parent?.routes[0]; if (parentRoute) { return { routes: [ { ...parentRoute, state: addState(parentRoute.state), }, ], }; } return state; }; return addState(parentFocusedRouteState); }, [ parentFocusedRouteState, route.key, route.name, route.params, route.path, ]); const context = React.useMemo( () => ({ state: routeState, getState: getCurrentState, setState: setCurrentState, getKey, setKey, getIsInitial, addOptionsGetter, }), [ routeState, getCurrentState, setCurrentState, getKey, setKey, getIsInitial, addOptionsGetter, ] ); const ScreenComponent = screen.getComponent ? screen.getComponent() : screen.component; return ( {ScreenComponent !== undefined ? ( ) : screen.children !== undefined ? ( screen.children({ navigation, route }) ) : null} ); }