Skip to content

Commit

Permalink
Merge pull request #44559 from Expensify/Rory-ReportScreenWrapper
Browse files Browse the repository at this point in the history
[CP Stg] Replace ReportScreenIDSetter with useLastAccessedReportID

(cherry picked from commit 359dbe6)
  • Loading branch information
roryabraham authored and OSBotify committed Jun 27, 2024
1 parent 09d246a commit b868a00
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 129 deletions.
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
2 changes: 1 addition & 1 deletion src/libs/Navigation/AppNavigator/CENTRAL_PANE_SCREENS.tsx
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

0 comments on commit b868a00

Please sign in to comment.