Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CP Stg] Replace ReportScreenIDSetter with useLastAccessedReportID #44559

Merged
merged 9 commits into from
Jun 27, 2024
2 changes: 1 addition & 1 deletion src/components/ScreenWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function ScreenWrapper(
) {
/**
* We are only passing navigation as prop from
* ReportScreenWrapper -> ReportScreen -> ScreenWrapper
* ReportScreen -> ScreenWrapper
*
* so in other places where ScreenWrapper is used, we need to
* fallback to useNavigation.
Expand Down
148 changes: 148 additions & 0 deletions src/hooks/useLastAccessedReportID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {useCallback, useSyncExternalStore} from 'react';
import type {OnyxCollection} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, Report, ReportMetadata} from '@src/types/onyx';
import useActiveWorkspace from './useActiveWorkspace';
import usePermissions from './usePermissions';

/*
* This hook is used to get the lastAccessedReportID.
* This is a piece of data that's derived from a lot of frequently-changing Onyx values: (reports, reportMetadata, policies, etc...)
* We don't want any component that needs access to the lastAccessedReportID to have to re-render any time any of those values change, just when the lastAccessedReportID changes.
* So we have a custom implementation in this file that leverages useSyncExternalStore to connect to a "store" of multiple Onyx values, and re-render only when the one derived value changes.
*/

const subscribers: Array<() => void> = [];

let reports: OnyxCollection<Report> = {};
let reportMetadata: OnyxCollection<ReportMetadata> = {};
let policies: OnyxCollection<Policy> = {};
let accountID: number | undefined;
let isFirstTimeNewExpensifyUser = false;

let reportsConnection: number;
let reportMetadataConnection: number;
let policiesConnection: number;
let accountIDConnection: number;
let isFirstTimeNewExpensifyUserConnection: number;

function notifySubscribers() {
subscribers.forEach((subscriber) => subscriber());
}

function subscribeToOnyxData() {
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
reportsConnection = Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
reports = value;
notifySubscribers();
},
});
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
reportMetadataConnection = Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_METADATA,
waitForCollectionCallback: true,
callback: (value) => {
reportMetadata = value;
notifySubscribers();
},
});
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
policiesConnection = Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => {
policies = value;
notifySubscribers();
},
});
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
accountIDConnection = Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (value) => {
accountID = value?.accountID;
notifySubscribers();
},
});
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
isFirstTimeNewExpensifyUserConnection = Onyx.connect({
key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
callback: (value) => {
isFirstTimeNewExpensifyUser = !!value;
notifySubscribers();
},
});
}

function unsubscribeFromOnyxData() {
if (reportsConnection) {
Onyx.disconnect(reportsConnection);
reportsConnection = 0;
}
if (reportMetadataConnection) {
Onyx.disconnect(reportMetadataConnection);
reportMetadataConnection = 0;
}
if (policiesConnection) {
Onyx.disconnect(policiesConnection);
policiesConnection = 0;
}
if (accountIDConnection) {
Onyx.disconnect(accountIDConnection);
accountIDConnection = 0;
}
if (isFirstTimeNewExpensifyUserConnection) {
Onyx.disconnect(isFirstTimeNewExpensifyUserConnection);
isFirstTimeNewExpensifyUserConnection = 0;
}
}

function removeSubscriber(subscriber: () => void) {
const subscriberIndex = subscribers.indexOf(subscriber);
if (subscriberIndex < 0) {
return;
}
subscribers.splice(subscriberIndex, 1);
if (subscribers.length === 0) {
unsubscribeFromOnyxData();
}
}

function addSubscriber(subscriber: () => void) {
subscribers.push(subscriber);
if (!reportsConnection) {
subscribeToOnyxData();
}
return () => removeSubscriber(subscriber);
}

/**
* Get the last accessed reportID.
*/
export default function useLastAccessedReportID(shouldOpenOnAdminRoom: boolean) {
const {canUseDefaultRooms} = usePermissions();
const {activeWorkspaceID} = useActiveWorkspace();

const getSnapshot = useCallback(() => {
const policyMemberAccountIDs = getPolicyEmployeeListByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID);
return ReportUtils.findLastAccessedReport(
reports,
!canUseDefaultRooms,
policies,
isFirstTimeNewExpensifyUser,
shouldOpenOnAdminRoom,
reportMetadata,
activeWorkspaceID,
policyMemberAccountIDs,
)?.reportID;
}, [activeWorkspaceID, canUseDefaultRooms, shouldOpenOnAdminRoom]);

// We need access to all the data from these Onyx.connect calls, but we don't want to re-render the consuming component
// unless the derived value (lastAccessedReportID) changes. To address these, we'll wrap everything with useSyncExternalStore
return useSyncExternalStore(addSubscriber, getSnapshot);
}
17 changes: 12 additions & 5 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import Onyx, {withOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import OptionsListContextProvider from '@components/OptionListContextProvider';
import useLastAccessedReportID from '@hooks/useLastAccessedReportID';
import useOnboardingLayout from '@hooks/useOnboardingLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -76,16 +77,21 @@ const loadReportAvatar = () => require<ReactComponentModule>('../../../pages/Rep
const loadReceiptView = () => require<ReactComponentModule>('../../../pages/TransactionReceiptPage').default;
const loadWorkspaceJoinUser = () => require<ReactComponentModule>('@pages/workspace/WorkspaceJoinUserPage').default;

function getCentralPaneScreenInitialParams(screenName: CentralPaneName): Partial<ValueOf<CentralPaneScreensParamList>> {
function shouldOpenOnAdminRoom() {
const url = getCurrentUrl();
const openOnAdminRoom = url ? new URL(url).searchParams.get('openOnAdminRoom') : undefined;
return url ? new URL(url).searchParams.get('openOnAdminRoom') === 'true' : false;
}

function getCentralPaneScreenInitialParams(screenName: CentralPaneName, lastAccessedReportID?: string): Partial<ValueOf<CentralPaneScreensParamList>> {
if (screenName === SCREENS.SEARCH.CENTRAL_PANE) {
return {sortBy: CONST.SEARCH.TABLE_COLUMNS.DATE, sortOrder: CONST.SEARCH.SORT_ORDER.DESC};
}

if (screenName === SCREENS.REPORT && openOnAdminRoom === 'true') {
return {openOnAdminRoom: true};
if (screenName === SCREENS.REPORT) {
return {
openOnAdminRoom: shouldOpenOnAdminRoom() ? true : undefined,
reportID: lastAccessedReportID,
};
}

return undefined;
Expand Down Expand Up @@ -191,6 +197,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
const StyleUtils = useStyleUtils();
const {isSmallScreenWidth} = useWindowDimensions();
const {shouldUseNarrowLayout} = useOnboardingLayout();
const lastAccessedReportID = useLastAccessedReportID(shouldOpenOnAdminRoom());
const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles, StyleUtils);
const onboardingModalScreenOptions = useMemo(() => screenOptions.onboardingModalNavigator(shouldUseNarrowLayout), [screenOptions, shouldUseNarrowLayout]);
const onboardingScreenOptions = useMemo(
Expand Down Expand Up @@ -461,7 +468,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
<RootStack.Screen
key={centralPaneName}
name={centralPaneName}
initialParams={getCentralPaneScreenInitialParams(centralPaneName)}
initialParams={getCentralPaneScreenInitialParams(centralPaneName, lastAccessedReportID)}
getComponent={componentGetter}
options={CentralPaneScreenOptions}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const CENTRAL_PANE_SCREENS = {
[SCREENS.SETTINGS.SAVE_THE_WORLD]: withPrepareCentralPaneScreen(() => require<ReactComponentModule>('../../../pages/TeachersUnite/SaveTheWorldPage').default),
[SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: withPrepareCentralPaneScreen(() => require<ReactComponentModule>('../../../pages/settings/Subscription/SubscriptionSettingsPage').default),
[SCREENS.SEARCH.CENTRAL_PANE]: withPrepareCentralPaneScreen(() => require<ReactComponentModule>('../../../pages/Search/SearchPage').default),
[SCREENS.REPORT]: withPrepareCentralPaneScreen(() => require<ReactComponentModule>('./ReportScreenWrapper').default),
[SCREENS.REPORT]: withPrepareCentralPaneScreen(() => require<ReactComponentModule>('../../../pages/home/ReportScreen').default),
} satisfies Screens;

export default CENTRAL_PANE_SCREENS;
91 changes: 0 additions & 91 deletions src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts

This file was deleted.

30 changes: 0 additions & 30 deletions src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ function isNumeric(value: string): boolean {
if (typeof value !== 'string') {
return false;
}
return /^\d*$/.test(value);
return CONST.REGEX.NUMBER.test(value);
}

/**
Expand Down
Loading
Loading