From 61f7064a01745af6bea2f1efea3168c351af1d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 20 Oct 2023 11:22:38 +0200 Subject: [PATCH 1/7] add useOnyxContext hook --- src/components/OnyxProvider.tsx | 3 ++- src/components/createOnyxContext.tsx | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/OnyxProvider.tsx b/src/components/OnyxProvider.tsx index 3bd4ca52c3be..8682e832debc 100644 --- a/src/components/OnyxProvider.tsx +++ b/src/components/OnyxProvider.tsx @@ -6,7 +6,7 @@ import ComposeProviders from './ComposeProviders'; // Set up any providers for individual keys. This should only be used in cases where many components will subscribe to // the same key (e.g. FlatList renderItem components) const [withNetwork, NetworkProvider, NetworkContext] = createOnyxContext(ONYXKEYS.NETWORK); -const [withPersonalDetails, PersonalDetailsProvider] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS_LIST); +const [withPersonalDetails, PersonalDetailsProvider, , usePersonalDetails] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS_LIST); const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE); const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); const [withBlockedFromConcierge, BlockedFromConciergeProvider] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); @@ -45,6 +45,7 @@ export default OnyxProvider; export { withNetwork, withPersonalDetails, + usePersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, diff --git a/src/components/createOnyxContext.tsx b/src/components/createOnyxContext.tsx index d142e551012f..a0ac5942b098 100644 --- a/src/components/createOnyxContext.tsx +++ b/src/components/createOnyxContext.tsx @@ -1,4 +1,4 @@ -import React, {ComponentType, ForwardRefExoticComponent, ForwardedRef, PropsWithoutRef, ReactNode, RefAttributes, createContext, forwardRef} from 'react'; +import React, {ComponentType, ForwardRefExoticComponent, ForwardedRef, PropsWithoutRef, ReactNode, RefAttributes, createContext, forwardRef, useContext} from 'react'; import {withOnyx} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import getComponentDisplayName from '../libs/getComponentDisplayName'; @@ -29,7 +29,12 @@ type WithOnyxKey = WrapComponentWithConsumer; // createOnyxContext return type -type CreateOnyxContext = [WithOnyxKey, ComponentType, TOnyxKey>>, React.Context>]; +type CreateOnyxContext = [ + WithOnyxKey, + ComponentType, TOnyxKey>>, + React.Context>, + () => OnyxValues[TOnyxKey], +]; export default (onyxKeyName: TOnyxKey): CreateOnyxContext => { const Context = createContext>(null); @@ -77,5 +82,13 @@ export default (onyxKeyName: TOnyxKey): CreateOnyxCon }; } - return [withOnyxKey, ProviderWithOnyx, Context]; + const useOnyxContext = () => { + const value = useContext(Context); + if (value === null) { + throw new Error(`useOnyxContext must be used within a OnyxProvider [key: ${onyxKeyName}]`); + } + return value; + }; + + return [withOnyxKey, ProviderWithOnyx, Context, useOnyxContext]; }; From 93f9ac7e54069acfe2333c53aa9ff8ef9ab76975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 20 Oct 2023 11:42:42 +0200 Subject: [PATCH 2/7] ReportActionItem swap withPersonalDetails to usePersonalDetails --- src/pages/home/report/ReportActionItem.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 42bcfd49f207..7a42512a3c48 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -28,7 +28,7 @@ import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMe import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction'; -import {withBlockedFromConcierge, withNetwork, withPersonalDetails, withReportActionsDrafts} from '../../../components/OnyxProvider'; +import {usePersonalDetails, withBlockedFromConcierge, withNetwork, withReportActionsDrafts} from '../../../components/OnyxProvider'; import RenameAction from '../../../components/ReportActionItem/RenameAction'; import InlineSystemMessage from '../../../components/InlineSystemMessage'; import styles from '../../../styles/styles'; @@ -49,7 +49,6 @@ import Icon from '../../../components/Icon'; import * as Expensicons from '../../../components/Icon/Expensicons'; import Text from '../../../components/Text'; import DisplayNames from '../../../components/DisplayNames'; -import personalDetailsPropType from '../../personalDetailsPropType'; import ReportPreview from '../../../components/ReportActionItem/ReportPreview'; import ReportActionItemDraft from './ReportActionItemDraft'; import TaskPreview from '../../../components/ReportActionItem/TaskPreview'; @@ -111,7 +110,6 @@ const propTypes = { ...windowDimensionsPropTypes, emojiReactions: EmojiReactionsPropTypes, - personalDetailsList: PropTypes.objectOf(personalDetailsPropType), /** IOU report for this action, if any */ iouReport: reportPropTypes, @@ -127,7 +125,6 @@ const defaultProps = { draftMessage: '', preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, emojiReactions: {}, - personalDetailsList: {}, shouldShowSubscriptAvatar: false, hasOutstandingIOU: false, iouReport: undefined, @@ -136,6 +133,7 @@ const defaultProps = { }; function ReportActionItem(props) { + const personalDetails = usePersonalDetails(); const [isContextMenuActive, setIsContextMenuActive] = useState(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); @@ -362,7 +360,7 @@ function ReportActionItem(props) { /> ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); const paymentType = lodashGet(props.action, 'originalMessage.paymentType', ''); const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(props.report.reportID) && !ReportUtils.isSettled(props.report.reportID); @@ -508,7 +506,7 @@ function ReportActionItem(props) { numberOfReplies={numberOfThreadReplies} mostRecentReply={`${props.action.childLastVisibleActionCreated}`} isHovered={hovered} - icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs, props.personalDetailsList)} + icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs, personalDetails)} onSecondaryInteraction={showPopover} /> @@ -628,7 +626,7 @@ function ReportActionItem(props) { const isWhisper = whisperedToAccountIDs.length > 0; const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); - const whisperedToPersonalDetails = isWhisper ? _.filter(props.personalDetailsList, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; + const whisperedToPersonalDetails = isWhisper ? _.filter(personalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( Date: Fri, 20 Oct 2023 11:44:24 +0200 Subject: [PATCH 3/7] ReportActionItemSingle swap withPersonalDetails to usePersonalDetails --- .../home/report/ReportActionItemSingle.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 60de11bdf218..23b0af3bc005 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -9,12 +9,11 @@ import ReportActionItemFragment from './ReportActionItemFragment'; import styles from '../../../styles/styles'; import ReportActionItemDate from './ReportActionItemDate'; import Avatar from '../../../components/Avatar'; -import personalDetailsPropType from '../../personalDetailsPropType'; import compose from '../../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; -import {withPersonalDetails} from '../../../components/OnyxProvider'; +import {usePersonalDetails} from '../../../components/OnyxProvider'; import ControlSelection from '../../../libs/ControlSelection'; import * as ReportUtils from '../../../libs/ReportUtils'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; @@ -37,9 +36,6 @@ const propTypes = { /** All the data of the action */ action: PropTypes.shape(reportActionPropTypes).isRequired, - /** All of the personalDetails */ - personalDetailsList: PropTypes.objectOf(personalDetailsPropType), - /** Styles for the outermost View */ // eslint-disable-next-line react/forbid-prop-types wrapperStyles: PropTypes.arrayOf(PropTypes.object), @@ -69,7 +65,6 @@ const propTypes = { }; const defaultProps = { - personalDetailsList: {}, wrapperStyles: [styles.chatItem], showHeader: true, shouldShowSubscriptAvatar: false, @@ -88,9 +83,10 @@ const showWorkspaceDetails = (reportID) => { }; function ReportActionItemSingle(props) { + const personalDetails = usePersonalDetails(); const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; - let {displayName} = props.personalDetailsList[actorAccountID] || {}; - const {avatar, login, pendingFields, status, fallbackIcon} = props.personalDetailsList[actorAccountID] || {}; + let {displayName} = personalDetails[actorAccountID] || {}; + const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {}; let actorHint = (login || displayName || '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const displayAllActors = useMemo(() => props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport, [props.action.actionName, props.iouReport]); const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(props.report) && (!actorAccountID || displayAllActors); @@ -100,10 +96,10 @@ function ReportActionItemSingle(props) { displayName = ReportUtils.getPolicyName(props.report); actorHint = displayName; avatarSource = ReportUtils.getWorkspaceAvatar(props.report); - } else if (props.action.delegateAccountID && props.personalDetailsList[props.action.delegateAccountID]) { + } else if (props.action.delegateAccountID && personalDetails[props.action.delegateAccountID]) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their // details. This will be improved upon when the Copilot feature is implemented. - const delegateDetails = props.personalDetailsList[props.action.delegateAccountID]; + const delegateDetails = personalDetails[props.action.delegateAccountID]; const delegateDisplayName = delegateDetails.displayName; actorHint = `${delegateDisplayName} (${props.translate('reportAction.asCopilot')} ${displayName})`; displayName = actorHint; @@ -116,7 +112,7 @@ function ReportActionItemSingle(props) { if (displayAllActors) { // The ownerAccountID and actorAccountID can be the same if the a user requests money back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice const secondaryAccountId = props.iouReport.ownerAccountID === actorAccountID ? props.iouReport.managerID : props.iouReport.ownerAccountID; - const secondaryUserDetails = props.personalDetailsList[secondaryAccountId] || {}; + const secondaryUserDetails = personalDetails[secondaryAccountId] || {}; const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { @@ -270,7 +266,6 @@ ReportActionItemSingle.displayName = 'ReportActionItemSingle'; export default compose( withLocalize, - withPersonalDetails(), withOnyx({ betas: { key: ONYXKEYS.BETAS, From 946ad2be25cf7f74a9eb0ef60a093ecf4812763f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 20 Oct 2023 11:45:54 +0200 Subject: [PATCH 4/7] withCurrentUserPersonalDetails swap withOnyx to usePersonalDetails --- src/components/withCurrentUserPersonalDetails.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/withCurrentUserPersonalDetails.js b/src/components/withCurrentUserPersonalDetails.js index 7a47ea7cc712..36c2eb67a2f1 100644 --- a/src/components/withCurrentUserPersonalDetails.js +++ b/src/components/withCurrentUserPersonalDetails.js @@ -5,6 +5,7 @@ import getComponentDisplayName from '../libs/getComponentDisplayName'; import ONYXKEYS from '../ONYXKEYS'; import personalDetailsPropType from '../pages/personalDetailsPropType'; import refPropTypes from './refPropTypes'; +import {usePersonalDetails} from './OnyxProvider'; const withCurrentUserPersonalDetailsPropTypes = { currentUserPersonalDetails: personalDetailsPropType, @@ -18,9 +19,6 @@ export default function (WrappedComponent) { const propTypes = { forwardedRef: refPropTypes, - /** Personal details of all the users, including current user */ - personalDetails: PropTypes.objectOf(personalDetailsPropType), - /** Session of the current user */ session: PropTypes.shape({ accountID: PropTypes.number, @@ -28,15 +26,15 @@ export default function (WrappedComponent) { }; const defaultProps = { forwardedRef: undefined, - personalDetails: {}, session: { accountID: 0, }, }; function WithCurrentUserPersonalDetails(props) { + const personalDetails = usePersonalDetails(); const accountID = props.session.accountID; - const accountPersonalDetails = props.personalDetails[accountID]; + const accountPersonalDetails = personalDetails[accountID]; const currentUserPersonalDetails = useMemo(() => ({...accountPersonalDetails, accountID}), [accountPersonalDetails, accountID]); return ( Date: Fri, 20 Oct 2023 13:22:02 +0200 Subject: [PATCH 5/7] withCurrentUserPersonalDetails add default value for back-compat --- src/components/withCurrentUserPersonalDetails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/withCurrentUserPersonalDetails.js b/src/components/withCurrentUserPersonalDetails.js index 36c2eb67a2f1..a6a417a9087b 100644 --- a/src/components/withCurrentUserPersonalDetails.js +++ b/src/components/withCurrentUserPersonalDetails.js @@ -32,7 +32,7 @@ export default function (WrappedComponent) { }; function WithCurrentUserPersonalDetails(props) { - const personalDetails = usePersonalDetails(); + const personalDetails = usePersonalDetails() || {}; const accountID = props.session.accountID; const accountPersonalDetails = personalDetails[accountID]; const currentUserPersonalDetails = useMemo(() => ({...accountPersonalDetails, accountID}), [accountPersonalDetails, accountID]); From e363e0078a568c9d32a2cc3106d9d4384beb096e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 20 Oct 2023 13:23:18 +0200 Subject: [PATCH 6/7] add default values for back-compat --- src/pages/home/report/ReportActionItem.js | 2 +- src/pages/home/report/ReportActionItemSingle.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 7a42512a3c48..6f70b67400a8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -133,7 +133,7 @@ const defaultProps = { }; function ReportActionItem(props) { - const personalDetails = usePersonalDetails(); + const personalDetails = usePersonalDetails() || {}; const [isContextMenuActive, setIsContextMenuActive] = useState(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 23b0af3bc005..38319afd60b2 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -83,7 +83,7 @@ const showWorkspaceDetails = (reportID) => { }; function ReportActionItemSingle(props) { - const personalDetails = usePersonalDetails(); + const personalDetails = usePersonalDetails() || {}; const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; let {displayName} = personalDetails[actorAccountID] || {}; const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {}; From df96e099dc4047c84708639682f214d6ff22a682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 20 Oct 2023 13:31:37 +0200 Subject: [PATCH 7/7] replace inline instances of empty object with a reference to EMPTY_OBJECT --- src/components/withCurrentUserPersonalDetails.js | 3 ++- src/pages/home/report/ReportActionItem.js | 2 +- src/pages/home/report/ReportActionItemSingle.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/withCurrentUserPersonalDetails.js b/src/components/withCurrentUserPersonalDetails.js index a6a417a9087b..82fcada1b3d1 100644 --- a/src/components/withCurrentUserPersonalDetails.js +++ b/src/components/withCurrentUserPersonalDetails.js @@ -6,6 +6,7 @@ import ONYXKEYS from '../ONYXKEYS'; import personalDetailsPropType from '../pages/personalDetailsPropType'; import refPropTypes from './refPropTypes'; import {usePersonalDetails} from './OnyxProvider'; +import CONST from '../CONST'; const withCurrentUserPersonalDetailsPropTypes = { currentUserPersonalDetails: personalDetailsPropType, @@ -32,7 +33,7 @@ export default function (WrappedComponent) { }; function WithCurrentUserPersonalDetails(props) { - const personalDetails = usePersonalDetails() || {}; + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const accountID = props.session.accountID; const accountPersonalDetails = personalDetails[accountID]; const currentUserPersonalDetails = useMemo(() => ({...accountPersonalDetails, accountID}), [accountPersonalDetails, accountID]); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 6f70b67400a8..1aeacb3fd3fd 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -133,7 +133,7 @@ const defaultProps = { }; function ReportActionItem(props) { - const personalDetails = usePersonalDetails() || {}; + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 38319afd60b2..fc189a3aef36 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -83,7 +83,7 @@ const showWorkspaceDetails = (reportID) => { }; function ReportActionItemSingle(props) { - const personalDetails = usePersonalDetails() || {}; + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; let {displayName} = personalDetails[actorAccountID] || {}; const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {};