diff --git a/src/hooks/useDeepCompareRef.ts b/src/hooks/useDeepCompareRef.ts new file mode 100644 index 000000000000..7511c1516a1d --- /dev/null +++ b/src/hooks/useDeepCompareRef.ts @@ -0,0 +1,24 @@ +import isEqual from 'lodash/isEqual'; +import {useRef} from 'react'; + +/** + * This hook returns a reference to the provided value, + * but only updates that reference if a deep comparison indicates that the value has changed. + * + * This is useful when working with objects or arrays as dependencies to other hooks like `useEffect` or `useMemo`, + * where you want the hook to trigger not just on reference changes, but also when the contents of the object or array change. + * + * @example + * const myArray = // some array + * const deepComparedArray = useDeepCompareRef(myArray); + * useEffect(() => { + * // This will run not just when myArray is a new array, but also when its contents change. + * }, [deepComparedArray]); + */ +export default function useDeepCompareRef(value: T): T | undefined { + const ref = useRef(); + if (!isEqual(value, ref.current)) { + ref.current = value; + } + return ref.current; +} diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 5696f0b800f4..414745270b68 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -21,6 +21,7 @@ import TaskHeaderActionButton from '@components/TaskHeaderActionButton'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import withCurrentReportID from '@components/withCurrentReportID'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; +import useDeepCompareRef from '@hooks/useDeepCompareRef'; import useIsReportOpenInRHP from '@hooks/useIsReportOpenInRHP'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -153,6 +154,7 @@ function ReportScreen({ }); const isLoadingReportOnyx = isLoadingOnyxValue(reportResult); + const permissions = useDeepCompareRef(reportOnyx?.permissions); /** * Create a lightweight Report so as to keep the re-rendering as light as possible by @@ -201,7 +203,7 @@ function ReportScreen({ isOptimisticReport: reportOnyx?.isOptimisticReport, lastMentionedTime: reportOnyx?.lastMentionedTime, avatarUrl: reportOnyx?.avatarUrl, - permissions: reportOnyx?.permissions, + permissions, invoiceReceiver: reportOnyx?.invoiceReceiver, }), [ @@ -242,7 +244,7 @@ function ReportScreen({ reportOnyx?.isOptimisticReport, reportOnyx?.lastMentionedTime, reportOnyx?.avatarUrl, - reportOnyx?.permissions, + permissions, reportOnyx?.invoiceReceiver, ], ); @@ -711,6 +713,7 @@ function ReportScreen({ onComposerFocus={() => setIsComposerFocus(true)} onComposerBlur={() => setIsComposerFocus(false)} report={report} + reportMetadata={reportMetadata} reportNameValuePairs={reportNameValuePairs} pendingAction={reportPendingAction} isComposerFullSize={!!isComposerFullSize} diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index ac56fe916bc9..decd8f1b8d56 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -40,6 +40,8 @@ type ReportFooterProps = ReportFooterOnyxProps & { /** Report object for the current report */ report?: OnyxTypes.Report; + reportMetadata?: OnyxEntry; + reportNameValuePairs?: OnyxEntry; /** The last report action */ @@ -72,6 +74,7 @@ function ReportFooter({ pendingAction, session, report = {reportID: '0'}, + reportMetadata, reportNameValuePairs, shouldShowComposeInput = false, isEmptyChat = true, @@ -90,7 +93,10 @@ function ReportFooter({ const isAnonymousUser = session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; const isSmallSizeLayout = windowWidth - (isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; - const hideComposer = !ReportUtils.canUserPerformWriteAction(report, reportNameValuePairs) || blockedFromChat; + + // If a user just signed in and is viewing a public report, optimistically show the composer while loading the report, since they will have write access when the response comes back. + const showComposerOptimistically = !isAnonymousUser && ReportUtils.isPublicRoom(report) && reportMetadata?.isLoadingInitialReportActions; + const hideComposer = (!ReportUtils.canUserPerformWriteAction(report, reportNameValuePairs) && !showComposerOptimistically) || blockedFromChat; const canWriteInReport = ReportUtils.canWriteInReport(report); const isSystemChat = ReportUtils.isSystemChat(report); @@ -211,6 +217,7 @@ export default withOnyx({ prevProps.lastReportAction === nextProps.lastReportAction && prevProps.shouldShowComposeInput === nextProps.shouldShowComposeInput && prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay && - lodashIsEqual(prevProps.session, nextProps.session), + lodashIsEqual(prevProps.session, nextProps.session) && + lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata), ), );