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

Use initialValue in withOnyx and other optimizations for ReportScreen #26772

Merged
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3be5e69
Separate ReportScreenContext to prevent cyclic re-renders
ospfranco Aug 25, 2023
6125c38
Get rid of full list re-render when marking messages as unread
ospfranco Aug 28, 2023
be7ea56
Linting
ospfranco Aug 28, 2023
caed994
Merge branch 'main' into osp/ReportScreen-optimizations
ospfranco Aug 29, 2023
37926ee
Merge branch 'main' into osp/ReportScreen-optimizations
ospfranco Aug 29, 2023
90c9119
create new onyx metadata collection and connect components
ospfranco Aug 29, 2023
9829fd8
Clean code on ReportScreen main body
ospfranco Aug 30, 2023
bbeb353
Merge branch 'main' into osp/report-metadata
ospfranco Aug 30, 2023
426c3cb
Do not use context on SidebarLinks
ospfranco Aug 30, 2023
9fd2f94
Eagerly initialize date-fns locale
ospfranco Aug 30, 2023
b3b33eb
Update src/pages/home/ReportScreen.js
ospfranco Aug 30, 2023
40d48bb
Update src/pages/home/report/ReportActionsList.js
ospfranco Aug 30, 2023
14808d9
Remove requestIdleCallback as it might not be available everywhere
ospfranco Aug 30, 2023
4f75359
Merge branch 'main' into osp/report-metadata
ospfranco Sep 4, 2023
6effb37
Fix wrong variable name
ospfranco Sep 8, 2023
3619a25
Report screen performance improvements
ospfranco Sep 5, 2023
eb44749
Prevent SidebarLinks from re-rendering on opening report
ospfranco Sep 11, 2023
a77072a
Change delayUpdates prop on ReportScreen to new shouldDelayUpdates
ospfranco Sep 12, 2023
76fafe0
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 12, 2023
5d3f53b
Merge branch 'main' into osp/report-metadata
ospfranco Sep 12, 2023
cfa7db6
Merge branch 'main' into osp/report-metadata
ospfranco Sep 13, 2023
ceda644
Remove options object from ReportScreen withOnyx, replace with should…
ospfranco Sep 14, 2023
12c8fa9
Get rid of unused currentReportId prop
ospfranco Sep 14, 2023
d761db0
Merge branch 'main' into osp/report-metadata
ospfranco Sep 14, 2023
ffaf7a8
Remove containerHeight from ReportActionsSkeletonView because onLayou…
ospfranco Sep 14, 2023
3df387b
Lint
ospfranco Sep 14, 2023
8d5031b
Merge branch 'main' into osp/report-metadata
ospfranco Sep 15, 2023
063d658
Removed skeleton height calculation
ospfranco Sep 15, 2023
f74d674
Merge branch 'main' into osp/report-metadata
ospfranco Sep 18, 2023
f6b382c
Merge branch 'osp/report-metadata' into osp/further-report-screen-opt…
ospfranco Sep 18, 2023
42d54fa
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 20, 2023
6985019
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 20, 2023
035874c
Fix botched merge
ospfranco Sep 21, 2023
5628d27
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 21, 2023
492f847
Change onListLayout from actions to container
ospfranco Sep 21, 2023
dd43fd0
Add check before calling onLayout on ReportActionsView
ospfranco Sep 21, 2023
9eddbea
Completely remove onLayout prop from ReportActionsView
ospfranco Sep 21, 2023
66670d5
PR comments
ospfranco Sep 25, 2023
8f27192
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 25, 2023
4ea2342
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 25, 2023
4c2abca
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 25, 2023
6ecee83
Fix memo on ReportActionItem to re-render when a task is canceled
ospfranco Sep 25, 2023
e3fd39e
Replace withNetwork HOC with useNetwork hook
ospfranco Sep 25, 2023
3825371
Revert firstRenderRef calculation of isLoading flag
ospfranco Sep 26, 2023
24710b7
Move firstRenderRef from isLoading to shouldShowNotFoundPage
ospfranco Sep 26, 2023
ee7f4c7
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 26, 2023
f006f1d
PR Comments
ospfranco Sep 27, 2023
3aaef2f
Merge branch 'main' into osp/further-report-screen-optimizations
ospfranco Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ const ONYXKEYS = {
POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_',
WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_',
REPORT: 'report_',
REPORT_METADATA: 'reportMetadata_',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request: can we please add a comment to explain what belongs in report_ and what belongs in reportMetadata_. Without all the context the distinction isn't very clear.

Copy link
Contributor Author

@ospfranco ospfranco Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now we've separated the loading states isLoadingReportActions and isLoadingMoreReportActions into reportMetadata_, as they were causing many re-renders on smaller components every time the report actions were being fetched. It makes sense to add it a comment for future reference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, sounds good. Maybe the distinction is that REPORT_METADATA is for client-generated data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a performance optimization, not a hard cut between functionality/logic.

REPORT_ACTIONS: 'reportActions_',
REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_',
REPORT_ACTIONS_REACTIONS: 'reportActionsReactions_',
Expand Down Expand Up @@ -379,6 +380,7 @@ type OnyxValues = {
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember;
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record<string, number>;
[ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report;
[ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata;
[ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction;
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string;
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions;
Expand Down
1 change: 1 addition & 0 deletions src/components/ExceededCommentLength.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ ExceededCommentLength.displayName = 'ExceededCommentLength';
export default withOnyx({
comment: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
initialValue: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
initialValue: null,
initialValue: '',

Not sure how initialValue should work, but the default prop value is set as an empty string. Perhaps we should also set it as an empty string here?

},
})(ExceededCommentLength);
5 changes: 3 additions & 2 deletions src/components/Reactions/ReportActionItemEmojiReactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import EmojiReactionsPropTypes from './EmojiReactionsPropTypes';
import Tooltip from '../Tooltip';
import ReactionTooltipContent from './ReactionTooltipContent';
import * as EmojiUtils from '../../libs/EmojiUtils';
import ReportScreenContext from '../../pages/home/ReportScreenContext';
import {ReactionListContext} from '../../pages/home/ReportScreenContext';

const propTypes = {
emojiReactions: EmojiReactionsPropTypes,
Expand Down Expand Up @@ -41,8 +41,9 @@ const defaultProps = {
};

function ReportActionItemEmojiReactions(props) {
const {reactionListRef} = useContext(ReportScreenContext);
const reactionListRef = useContext(ReactionListContext);
const popoverReactionListAnchors = useRef({});

let totalReactionCount = 0;

// Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone
Expand Down
2 changes: 2 additions & 0 deletions src/components/ReportActionItem/TaskPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ export default compose(
withOnyx({
taskReport: {
key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
initialValue: {},
},
personalDetailsList: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
initialValue: {},
},
}),
)(TaskPreview);
7 changes: 2 additions & 5 deletions src/components/ReportActionsSkeletonView/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import {View, Dimensions} from 'react-native';
import SkeletonViewLines from './SkeletonViewLines';
import CONST from '../../CONST';

const propTypes = {
/** Height of the container component */
containerHeight: PropTypes.number.isRequired,

/** Whether to animate the skeleton view */
shouldAnimate: PropTypes.bool,
};
Expand All @@ -18,7 +15,7 @@ const defaultProps = {

function ReportActionsSkeletonView(props) {
// Determines the number of content items based on container height
const possibleVisibleContentItems = Math.ceil(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
const possibleVisibleContentItems = Math.ceil(Dimensions.get('window').height / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
const skeletonViewLines = [];
for (let index = 0; index < possibleVisibleContentItems; index++) {
const iconIndex = (index + 1) % 4;
Expand Down
11 changes: 11 additions & 0 deletions src/components/withLocalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const withLocalizePropTypes = {
/** Formats a datetime to local date and time string */
datetimeToCalendarTime: PropTypes.func.isRequired,

/** Updates date-fns internal locale */
updateLocale: PropTypes.func.isRequired,

/** Returns a locally converted phone number for numbers from the same region
* and an internationally converted phone number with the country code for numbers from other regions */
formatPhoneNumber: PropTypes.func.isRequired,
Expand Down Expand Up @@ -79,6 +82,7 @@ class LocaleContextProvider extends React.Component {
numberFormat: this.numberFormat.bind(this),
datetimeToRelative: this.datetimeToRelative.bind(this),
datetimeToCalendarTime: this.datetimeToCalendarTime.bind(this),
updateLocale: this.updateLocale.bind(this),
formatPhoneNumber: this.formatPhoneNumber.bind(this),
fromLocaleDigit: this.fromLocaleDigit.bind(this),
toLocaleDigit: this.toLocaleDigit.bind(this),
Expand Down Expand Up @@ -122,6 +126,13 @@ class LocaleContextProvider extends React.Component {
return DateUtils.datetimeToCalendarTime(this.props.preferredLocale, datetime, includeTimezone, lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected'), isLowercase);
}

/**
* Updates date-fns internal locale to the user preferredLocale
*/
updateLocale() {
DateUtils.setLocale(this.props.preferredLocale);
}

/**
* @param {String} phoneNumber
* @returns {String}
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useReportScrollManager/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {useContext, useCallback} from 'react';
import ReportScreenContext from '../../pages/home/ReportScreenContext';
import {ActionListContext} from '../../pages/home/ReportScreenContext';

function useReportScrollManager() {
const {flatListRef} = useContext(ReportScreenContext);
const flatListRef = useContext(ActionListContext);

/**
* Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useReportScrollManager/index.native.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {useContext, useCallback} from 'react';
import ReportScreenContext from '../../pages/home/ReportScreenContext';
import {ActionListContext} from '../../pages/home/ReportScreenContext';

function useReportScrollManager() {
const {flatListRef} = useContext(ReportScreenContext);
const flatListRef = useContext(ActionListContext);

/**
* Scroll to the provided index.
Expand Down
1 change: 1 addition & 0 deletions src/libs/DateUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ const DateUtils = {
subtractMillisecondsFromDateTime,
getDateStringFromISOTimestamp,
getStatusUntilDate,
setLocale,
};

export default DateUtils;
129 changes: 129 additions & 0 deletions src/libs/Navigation/AppNavigator/ReportScreenIDSetter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {useEffect} from 'react';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../../../ONYXKEYS';
import * as ReportUtils from '../../ReportUtils';
import reportPropTypes from '../../../pages/reportPropTypes';
import {withNavigationPropTypes} from '../../../components/withNavigation';
import * as App from '../../actions/App';
import usePermissions from '../../../hooks/usePermissions';
import CONST from '../../../CONST';
import Navigation from '../Navigation';

const propTypes = {
/** Available reports that would be displayed in this navigator */
reports: PropTypes.objectOf(reportPropTypes),

/** The policies which the user has access to */
policies: PropTypes.objectOf(
PropTypes.shape({
/** The policy name */
name: PropTypes.string,

/** The type of the policy */
type: PropTypes.string,
}),
),

isFirstTimeNewExpensifyUser: PropTypes.bool,

/** Navigation route context info provided by react navigation */
route: PropTypes.shape({
/** Route specific parameters used on this screen */
params: PropTypes.shape({
/** If the admin room should be opened */
openOnAdminRoom: PropTypes.bool,

/** The ID of the report this screen should display */
reportID: PropTypes.string,
}),
}).isRequired,

...withNavigationPropTypes,
};

const defaultProps = {
reports: {},
policies: {},
isFirstTimeNewExpensifyUser: false,
};

/**
* Get the most recently accessed report for the user
*
* @param {Object} reports
* @param {Boolean} [ignoreDefaultRooms]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not seem to have a default value set, also there are required params coming after

Suggested change
* @param {Boolean} [ignoreDefaultRooms]
* @param {Boolean} ignoreDefaultRooms

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand. This is just an internal function and doesn't require a default value, it should always be called with a boolean value:

CleanShot 2023-09-27 at 08 15 37

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but the [] in js docs means optional parameter, but this is not optional parameter but the looks of things

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this wasn't my code, just moved it from the wrapper. But it is fixed.

* @param {Object} policies
* @param {Boolean} isFirstTimeNewExpensifyUser
* @param {Boolean} openOnAdminRoom
* @returns {Number}
*/
const getLastAccessedReportID = (reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom) => {
// If deeplink url is of an attachment, we should show the report that the attachment comes from.
const currentRoute = Navigation.getActiveRoute();
const matches = CONST.REGEX.ATTACHMENT_ROUTE.exec(currentRoute);
const reportID = lodashGet(matches, 1, null);
if (reportID) {
return reportID;
}

const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom);

return lodashGet(lastReport, 'reportID');
};

// This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
function ReportScreenIDSetter(props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new component and we prefer destructuring the props, can you please do that here?

const {canUseDefaultRooms} = usePermissions();

useEffect(() => {
// Don't update if there is a reportID in the params already
if (lodashGet(props.route, 'params.reportID', null)) {
App.confirmReadyToOpenApp();
return;
}

// If there is no reportID in route, try to find last accessed and use it for setParams
const reportID = getLastAccessedReportID(
props.reports,
!canUseDefaultRooms,
props.policies,
props.isFirstTimeNewExpensifyUser,
lodashGet(props.route, 'params.openOnAdminRoom', false),
);

// It's possible that props.reports aren't fully loaded yet
// in that case the reportID is undefined
if (reportID) {
performance.mark('ReportScreenConfirmer_hook');
performance.measure('ReportScreenConfirmer_hook_meassure', {start: 'ReportScreenConfirmer_hook', duration: 100});
mountiny marked this conversation as resolved.
Show resolved Hide resolved
props.navigation.setParams({reportID: String(reportID)});
} else {
App.confirmReadyToOpenApp();
}
}, [props.route, props.navigation, props.reports, canUseDefaultRooms, props.policies, props.isFirstTimeNewExpensifyUser]);

// The ReportScreen without the reportID set will display a skeleton
// until the reportID is loaded and set in the route param
return null;
}

ReportScreenIDSetter.propTypes = propTypes;
ReportScreenIDSetter.defaultProps = defaultProps;
ReportScreenIDSetter.displayName = 'ReportScreenIDSetter';

export default withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
allowStaleData: true,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
allowStaleData: true,
},
isFirstTimeNewExpensifyUser: {
key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
initialValue: false,
},
})(ReportScreenIDSetter);
Loading
Loading