-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21406 from margelo/perf/21022-sidebarlinks-perfor…
…mance [Fix #21022] Improve performance by reducing re-renders SidebarLinks
- Loading branch information
Showing
15 changed files
with
500 additions
and
319 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import {withOnyx} from 'react-native-onyx'; | ||
import lodashGet from 'lodash/get'; | ||
import _ from 'underscore'; | ||
import PropTypes from 'prop-types'; | ||
import React, {useEffect, useRef, useMemo} from 'react'; | ||
import {deepEqual} from 'fast-equals'; | ||
import {withReportCommentDrafts} from '../OnyxProvider'; | ||
import SidebarUtils from '../../libs/SidebarUtils'; | ||
import compose from '../../libs/compose'; | ||
import ONYXKEYS from '../../ONYXKEYS'; | ||
import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDefaultProps} from '../withCurrentReportID'; | ||
import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultProps} from './OptionRowLHN'; | ||
import * as Report from '../../libs/actions/Report'; | ||
import * as UserUtils from '../../libs/UserUtils'; | ||
import participantPropTypes from '../participantPropTypes'; | ||
import CONST from '../../CONST'; | ||
|
||
const propTypes = { | ||
/** If true will disable ever setting the OptionRowLHN to focused */ | ||
shouldDisableFocusOptions: PropTypes.bool, | ||
|
||
/** List of users' personal details */ | ||
personalDetails: PropTypes.objectOf(participantPropTypes), | ||
|
||
/** The preferred language for the app */ | ||
preferredLocale: PropTypes.string, | ||
|
||
/** The full data of the report */ | ||
// eslint-disable-next-line react/forbid-prop-types | ||
fullReport: PropTypes.object, | ||
|
||
...withCurrentReportIDPropTypes, | ||
...basePropTypes, | ||
}; | ||
|
||
const defaultProps = { | ||
shouldDisableFocusOptions: false, | ||
personalDetails: {}, | ||
fullReport: {}, | ||
preferredLocale: CONST.LOCALES.DEFAULT, | ||
...withCurrentReportIDDefaultProps, | ||
...baseDefaultProps, | ||
}; | ||
|
||
/* | ||
* This component gets the data from onyx for the actual | ||
* OptionRowLHN component. | ||
* The OptionRowLHN component is memoized, so it will only | ||
* re-render if the data really changed. | ||
*/ | ||
function OptionRowLHNData({shouldDisableFocusOptions, currentReportID, fullReport, personalDetails, preferredLocale, comment, ...propsToForward}) { | ||
const reportID = propsToForward.reportID; | ||
// We only want to pass a boolean to the memoized component, | ||
// instead of a changing number (so we prevent unnecessary re-renders). | ||
const isFocused = !shouldDisableFocusOptions && currentReportID === reportID; | ||
|
||
const optionItemRef = useRef(); | ||
const optionItem = useMemo(() => { | ||
// Note: ideally we'd have this as a dependent selector in onyx! | ||
const item = SidebarUtils.getOptionData(fullReport, personalDetails, preferredLocale); | ||
if (deepEqual(item, optionItemRef.current)) { | ||
return optionItemRef.current; | ||
} | ||
optionItemRef.current = item; | ||
return item; | ||
}, [fullReport, preferredLocale, personalDetails]); | ||
|
||
useEffect(() => { | ||
if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { | ||
return; | ||
} | ||
Report.setReportWithDraft(reportID, true); | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
|
||
return ( | ||
<OptionRowLHN | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...propsToForward} | ||
isFocused={isFocused} | ||
optionItem={optionItem} | ||
/> | ||
); | ||
} | ||
|
||
OptionRowLHNData.propTypes = propTypes; | ||
OptionRowLHNData.defaultProps = defaultProps; | ||
OptionRowLHNData.displayName = 'OptionRowLHNData'; | ||
|
||
/** | ||
* @param {Object} [personalDetails] | ||
* @returns {Object|undefined} | ||
*/ | ||
const personalDetailsSelector = (personalDetails) => | ||
_.reduce( | ||
personalDetails, | ||
(finalPersonalDetails, personalData, accountID) => { | ||
// It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails | ||
// eslint-disable-next-line no-param-reassign | ||
finalPersonalDetails[accountID] = { | ||
accountID: Number(accountID), | ||
login: personalData.login, | ||
displayName: personalData.displayName, | ||
firstName: personalData.firstName, | ||
avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), | ||
}; | ||
return finalPersonalDetails; | ||
}, | ||
{}, | ||
); | ||
|
||
/** | ||
* This component is rendered in a list. | ||
* On scroll we want to avoid that a item re-renders | ||
* just because the list has to re-render when adding more items. | ||
* Thats also why the React.memo is used on the outer component here, as we just | ||
* use it to prevent re-renders from parent re-renders. | ||
*/ | ||
export default React.memo( | ||
compose( | ||
withCurrentReportID, | ||
withReportCommentDrafts({ | ||
propName: 'comment', | ||
transformValue: (drafts, props) => { | ||
const draftKey = `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${props.reportID}`; | ||
return lodashGet(drafts, draftKey, ''); | ||
}, | ||
}), | ||
withOnyx({ | ||
fullReport: { | ||
key: (props) => ONYXKEYS.COLLECTION.REPORT + props.reportID, | ||
}, | ||
personalDetails: { | ||
key: ONYXKEYS.PERSONAL_DETAILS_LIST, | ||
selector: personalDetailsSelector, | ||
}, | ||
preferredLocale: { | ||
key: ONYXKEYS.NVP_PREFERRED_LOCALE, | ||
}, | ||
}), | ||
)(OptionRowLHNData), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.