Skip to content

Commit

Permalink
Add missing docs to navigation functions
Browse files Browse the repository at this point in the history
  • Loading branch information
WojtekBoman committed Nov 27, 2024
1 parent 88fb30e commit c7e18b6
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 38 deletions.
88 changes: 53 additions & 35 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import RELATIONS from './linkingConfig/RELATIONS';
import navigationRef from './navigationRef';
import type {NavigationPartialRoute, NavigationStateRoute, RootStackParamList, State} from './types';

// Get the sidebar screen parameters from the split navigator passed as a param
function getSidebarScreenParams(splitNavigatorRoute: NavigationStateRoute) {
if (splitNavigatorRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
return splitNavigatorRoute.state?.routes?.at(0)?.params;
Expand Down Expand Up @@ -68,18 +69,16 @@ function canNavigate(methodName: string, params: Record<string, unknown> = {}):
return false;
}

// Re-exporting the getTopmostReportId here to fill in default value for state. The getTopmostReportId isn't defined in this file to avoid cyclic dependencies.
// Extracts from the topmost report its id.
const getTopmostReportId = (state = navigationRef.getState()) => getTopmostReportParam(state, 'reportID');

// Re-exporting the getTopmostReportActionID here to fill in default value for state. The getTopmostReportActionID isn't defined in this file to avoid cyclic dependencies.
// Extracts from the topmost report its action id.
const getTopmostReportActionId = (state = navigationRef.getState()) => getTopmostReportParam(state, 'reportActionID');

// Re-exporting the closeRHPFlow here to fill in default value for navigationRef. The closeRHPFlow isn't defined in this file to avoid cyclic dependencies.
const closeRHPFlow = (ref = navigationRef) => originalCloseRHPFlow(ref);

/**
* Function that generates dynamic urls from paths passed from OldDot
*/
// Function that generates dynamic urls from paths passed from OldDot.
function parseHybridAppUrl(url: HybridAppRoute | Route): Route {
switch (url) {
case HYBRID_APP_ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL:
Expand All @@ -94,7 +93,7 @@ function parseHybridAppUrl(url: HybridAppRoute | Route): Route {
}
}

/** Returns the current active route */
// Returns the current active route.
function getActiveRoute(): string {
const currentRoute = navigationRef.current && navigationRef.current.getCurrentRoute();
if (!currentRoute?.name) {
Expand All @@ -109,7 +108,7 @@ function getActiveRoute(): string {

return '';
}

// Returns the route of a report opened in RHP.
function getReportRHPActiveRoute(): string {
if (isReportOpenInRHP(navigationRef.getRootState())) {
return getActiveRoute();
Expand Down Expand Up @@ -146,10 +145,14 @@ function navigate(route: Route = ROUTES.HOME, type?: string) {
pendingRoute = route;
return;
}
// linkTo(navigationRef.current, route, type, isActiveRoute(route));

linkTo(navigationRef.current, route, type);
}

/**
* When routes are compared to determine whether the fallback route passed to the goUp function is in the state,
* these parameters shouldn't be included in the comparison.
*/
const routeParamsIgnore = ['path', 'initial', 'params', 'state', 'screen', 'policyID'];

// If we use destructuring, we will get an error if any of the ignored properties are not present in the object.
Expand Down Expand Up @@ -186,13 +189,24 @@ function doesRouteMatchToMinimalActionPayload(route: NavigationStateRoute | Navi
return shallowCompare(routeParams, minimalActionParams);
}

// Checks whether the given state is the root navigator state
function isRootNavigatorState(state: State): state is State<RootStackParamList> {
return state.key === navigationRef.current?.getRootState().key;
}

type GoBackOptions = {
/** If we should compare params when searching for a route in state to go up to.
/**
* If we should compare params when searching for a route in state to go up to.
* There are situations where we want to compare params when going up e.g. goUp to a specific report.
* Sometimes we want to go up and update params of screen e.g. country picker.
* In that case we want to goUp to a country picker with any params so we don't compare them. */
* In that case we want to goUp to a country picker with any params so we don't compare them.
*/
compareParams?: boolean;

/**
* Specifies whether goBack should pop to top when invoked.
* Additionaly, to execute popToTop, set the value of the global variable ShouldPopAllStateOnUP to true using the setShouldPopAllStateOnUP function.
*/
shouldPopToTop?: boolean;
};

Expand All @@ -201,14 +215,17 @@ const defaultGoBackOptions: Required<GoBackOptions> = {
shouldPopToTop: false,
};

function isRootNavigatorState(state: State): state is State<RootStackParamList> {
return state.key === navigationRef.current?.getRootState().key;
}

/**
* Navigate to the given fallbackRoute taking into account whether it is possible to go back to this screen. Within one nested navigator, we can go back by any number
* of screens, but if as a result of going back we would have to remove more than one screen from the rootState,
* replace is performed so as not to lose the visited pages.
* If fallbackRoute is not found in the state, replace is also called then.
*
* @param fallbackRoute - The route to go up.
* @param options - Optional configuration that affects navigation logic, such as parameter comparison.
*/
function goUp(fallbackRoute: Route, options?: GoBackOptions) {
const compareParams = options?.compareParams ?? defaultGoBackOptions.compareParams;

if (!canNavigate('goBack')) {
if (!canNavigate('goUp')) {
return;
}

Expand All @@ -231,6 +248,7 @@ function goUp(fallbackRoute: Route, options?: GoBackOptions) {
return;
}

const compareParams = options?.compareParams ?? defaultGoBackOptions.compareParams;
const indexOfFallbackRoute = targetState.routes.findLastIndex((route) => doesRouteMatchToMinimalActionPayload(route, minimalAction, compareParams));

const distanceToPop = targetState.routes.length - indexOfFallbackRoute - 1;
Expand All @@ -242,8 +260,10 @@ function goUp(fallbackRoute: Route, options?: GoBackOptions) {
return;
}

// If we are not comparing params, we want to use navigate action because it will replace params in the route already existing in the state if necessary.
// This part will need refactor after migrating to react-navigation 7. We will use popTo instead.
/**
* If we are not comparing params, we want to use navigate action because it will replace params in the route already existing in the state if necessary.
* This part will need refactor after migrating to react-navigation 7. We will use popTo instead.
*/
if (!compareParams) {
navigationRef.current.dispatch(minimalAction);
return;
Expand All @@ -254,16 +274,14 @@ function goUp(fallbackRoute: Route, options?: GoBackOptions) {

/**
* @param fallbackRoute - Fallback route if pop/goBack action should, but is not possible within RHP
* @param shouldPopToTop - Should we navigate to LHN on back press
* @param options - Optional configuration that affects navigation logic, e.g. whether goBack should popToTop.
*/
function goBack(fallbackRoute?: Route, options?: GoBackOptions) {
if (!canNavigate('goBack')) {
return;
}

const shouldPopToTop = options?.shouldPopToTop ?? false;

if (shouldPopToTop) {
if (options?.shouldPopToTop) {
if (shouldPopAllStateOnUP) {
shouldPopAllStateOnUP = false;
navigationRef.current?.dispatch(StackActions.popToTop());
Expand Down Expand Up @@ -302,9 +320,7 @@ function goBack(fallbackRoute?: Route, options?: GoBackOptions) {
navigationRef.current?.goBack();
}

/**
* Reset the navigation state to Home page
*/
// Reset the navigation state to Home page
function resetToHome() {
const isNarrowLayout = getIsNarrowLayout();
const rootState = navigationRef.getRootState();
Expand All @@ -318,24 +334,20 @@ function resetToHome() {
navigationRef.dispatch({payload, type: 'REPLACE', target: rootState.key});
}

/**
* Update route params for the specified route.
*/
// Update route params for the specified route.
function setParams(params: Record<string, unknown>, routeKey = '') {
navigationRef.current?.dispatch({
...CommonActions.setParams(params),
source: routeKey,
});
}

/**
* Returns the current active route without the URL params
*/
// Returns the current active route without the URL params.
function getActiveRouteWithoutParams(): string {
return getActiveRoute().replace(/\?.*/, '');
}

/** Returns the active route name from a state event from the navigationRef */
// Returns the active route name from a state event from the navigationRef.
function getRouteNameFromStateEvent(event: EventArg<'state', false, NavigationContainerEventMap['state']['data']>): string | undefined {
if (!event.data.state) {
return;
Expand Down Expand Up @@ -415,12 +427,17 @@ function waitForProtectedRoutes() {
});
}

// Changes the currently selected policy in the app.
function switchPolicyID(policyID?: string) {
navigationRef.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.SWITCH_POLICY_ID, payload: {policyID}});
}

type NavigateToReportWithPolicyCheckPayload = {report?: OnyxEntry<Report>; reportID?: string; reportActionID?: string; referrer?: string; policyIDToCheck?: string};

/**
* Navigates to a report passed as a param (as an id or report object) and checks whether the target object belongs to the currently selected workspace.
* If not, the current workspace is set to global.
*/
function navigateToReportWithPolicyCheck({report, reportID, reportActionID, referrer, policyIDToCheck}: NavigateToReportWithPolicyCheckPayload, ref = navigationRef) {
const targetReport = reportID ? {reportID, ...ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]} : report;
const policyID = policyIDToCheck ?? getPolicyIDFromState(navigationRef.getRootState() as State<RootStackParamList>);
Expand Down Expand Up @@ -453,15 +470,16 @@ function navigateToReportWithPolicyCheck({report, reportID, reportActionID, refe
);
}

// @TODO In places where we use dismissModal with report arg we should do dismiss modal and then navigate to the report.
// We left it here to limit the number of changed files.
// Closes the modal navigator (RHP, LHP, onboarding).
const dismissModal = (reportID?: string, ref = navigationRef) => {
ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL});
if (!reportID) {
return;
}
isNavigationReady().then(() => navigateToReportWithPolicyCheck({reportID}));
};

// Dismisses the modal and opens the given report.
const dismissModalWithReport = (report: OnyxEntry<Report>) => {
dismissModal();
isNavigationReady().then(() => navigateToReportWithPolicyCheck({report}));
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/helpers/customGetPathFromState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {getPathFromState} from '@react-navigation/native';
import NAVIGATORS from '@src/NAVIGATORS';
import {isFullScreenName} from './isNavigatorName';

// This function adds the policyID param to the url.
const customGetPathFromState: typeof getPathFromState = (state, options) => {
const path = getPathFromState(state, options);
const fullScreenRoute = state.routes.findLast((route) => isFullScreenName(route.name));
Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/helpers/getOnboardingAdaptedState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type {NavigationState, PartialState} from '@react-navigation/native';
import SCREENS from '@src/SCREENS';

/**
* When we open the application via deeplink to a specific onboarding screen, we want the previous onboarding screens to be able to go back to them.
* Therefore, the paths of the previous screens are added here.
*/
export default function getOnboardingAdaptedState(state: PartialState<NavigationState>): PartialState<NavigationState> {
const onboardingRoute = state.routes.at(0);
if (!onboardingRoute || onboardingRoute.name === SCREENS.ONBOARDING.PURPOSE) {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/helpers/getPolicyIDFromState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import extractPolicyIDFromQuery from './extractPolicyIDFromQuery';
* returns policyID value if one exists in navigation state
*
* PolicyID in this app can be stored in two ways:
* - on most screens but NOT Search as `policyID` param (on bottom tab screens)
* - on NAVIGATORS.REPORTS_SPLIT_NAVIGATOR as `policyID` param
* - on Search related screens as policyID filter inside `q` (SearchQuery) param (only for SEARCH_CENTRAL_PANE)
*/
const getPolicyIDFromState = (state: State<RootStackParamList>): string | undefined => {
Expand Down
3 changes: 2 additions & 1 deletion src/libs/Navigation/helpers/getTopmostReportParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import SCREENS from '@src/SCREENS';
// This function is in a separate file than Navigation.ts to avoid cyclic dependency.

/**
* Find the last visited report screen in the navigation state and get the id of it.
* Find the last visited report screen in the navigation state and get its specific param (id or action id).
*
* @param state - The react-navigation state
* @param reportParam - param to get from the report route params
* @returns - It's possible that there is no report screen
*/

Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/helpers/isNavigatorName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const FULL_SCREENS_SET = new Set(FULL_SCREENS);
const SIDEBARS_SET = new Set(SIDEBARS);
const ONBOARDING_SCREENS_SET = new Set(ONBOARDING_SCREENS);

/**
* Functions defined below are used to check whether a screen belongs to a specific group.
* It is mainly used to filter routes in the navigation state.
*/
function checkIfScreenHasMatchingNameToSetValues<T extends string>(screen: string | undefined, set: Set<T>): screen is T {
if (!screen) {
return false;
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/helpers/isReportOpenInRHP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {NavigationState} from '@react-navigation/native';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';

// Determines whether the report page is opened in RHP.
const isReportOpenInRHP = (state: NavigationState | undefined): boolean => {
const lastRoute = state?.routes?.at(-1);
if (!lastRoute) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {BackHandler, NativeModules} from 'react-native';
import navigationRef from '@navigation/navigationRef';

// We need to do some custom handling for the back button on Android for actions related to the search page.
// We need to do some custom handling for the back button on Android for actions related to the hybrid app.
function setupCustomAndroidBackHandler() {
const onBackPress = () => {
const rootState = navigationRef.getRootState();
Expand Down

0 comments on commit c7e18b6

Please sign in to comment.