From 53fd25f45352de90babc8400afb320f4999fce6d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 3 Jan 2024 15:44:30 +0000 Subject: [PATCH 01/11] refactor(typescript): migrate withreportandprivatenotesornotfound --- .../withReportAndPrivateNotesOrNotFound.js | 130 ------------------ .../withReportAndPrivateNotesOrNotFound.tsx | 90 ++++++++++++ .../home/report/withReportOrNotFound.tsx | 14 +- 3 files changed, 98 insertions(+), 136 deletions(-) delete mode 100644 src/pages/home/report/withReportAndPrivateNotesOrNotFound.js create mode 100644 src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js deleted file mode 100644 index 3982dd5ab542..000000000000 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js +++ /dev/null @@ -1,130 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; -import * as Report from '@libs/actions/Report'; -import compose from '@libs/compose'; -import getComponentDisplayName from '@libs/getComponentDisplayName'; -import * as ReportUtils from '@libs/ReportUtils'; -import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; -import reportPropTypes from '@pages/reportPropTypes'; -import ONYXKEYS from '@src/ONYXKEYS'; -import withReportOrNotFound from './withReportOrNotFound'; - -const propTypes = { - /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component. - * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */ - forwardedRef: PropTypes.func, - - /** The report currently being looked at */ - report: reportPropTypes, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - /** Session of currently logged in user */ - session: PropTypes.shape({ - /** accountID of currently logged in user */ - accountID: PropTypes.number, - }), - - route: PropTypes.shape({ - /** Params from the URL path */ - params: PropTypes.shape({ - /** reportID and accountID passed via route: /r/:reportID/notes/:accountID */ - reportID: PropTypes.string, - accountID: PropTypes.string, - }), - }).isRequired, -}; - -const defaultProps = { - forwardedRef: () => {}, - report: {}, - session: { - accountID: null, - }, -}; - -export default function (WrappedComponent) { - // eslint-disable-next-line rulesdir/no-negated-variables - function WithReportAndPrivateNotesOrNotFound({forwardedRef, ...props}) { - const {route, report, network, session} = props; - const accountID = route.params.accountID; - const isPrivateNotesFetchTriggered = !_.isUndefined(report.isLoadingPrivateNotes); - - useEffect(() => { - // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. - if (isPrivateNotesFetchTriggered || network.isOffline) { - return; - } - - Report.getReportPrivateNote(report.reportID); - // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies - }, [report.reportID, network.isOffline, isPrivateNotesFetchTriggered]); - - const isPrivateNotesEmpty = accountID ? _.isEmpty(lodashGet(report, ['privateNotes', accountID, 'note'], '')) : _.isEmpty(report.privateNotes); - const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchTriggered || (isPrivateNotesEmpty && report.isLoadingPrivateNotes); - - // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = useMemo(() => { - // Show not found view if the report is archived, or if the note is not of current user. - if (ReportUtils.isArchivedRoom(report) || (accountID && Number(session.accountID) !== Number(accountID))) { - return true; - } - - // Don't show not found view if the notes are still loading, or if the notes are non-empty. - if (shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty) { - return false; - } - - // As notes being empty and not loading is a valid case, show not found view only in offline mode. - return network.isOffline; - }, [report, network.isOffline, accountID, session.accountID, isPrivateNotesEmpty, shouldShowFullScreenLoadingIndicator]); - - if (shouldShowFullScreenLoadingIndicator) { - return ; - } - - if (shouldShowNotFoundPage) { - return ; - } - - return ( - - ); - } - - WithReportAndPrivateNotesOrNotFound.propTypes = propTypes; - WithReportAndPrivateNotesOrNotFound.defaultProps = defaultProps; - WithReportAndPrivateNotesOrNotFound.displayName = `withReportAndPrivateNotesOrNotFound(${getComponentDisplayName(WrappedComponent)})`; - - // eslint-disable-next-line rulesdir/no-negated-variables - const WithReportAndPrivateNotesOrNotFoundWithRef = React.forwardRef((props, ref) => ( - - )); - - WithReportAndPrivateNotesOrNotFoundWithRef.displayName = 'WithReportAndPrivateNotesOrNotFoundWithRef'; - - return compose( - withReportOrNotFound(), - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), - withNetwork(), - )(WithReportAndPrivateNotesOrNotFoundWithRef); -} diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx new file mode 100644 index 000000000000..83e4248c81b6 --- /dev/null +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -0,0 +1,90 @@ +import {RouteProp} from '@react-navigation/core'; +import React, {ComponentType, ForwardedRef, forwardRef, RefAttributes, useEffect, useMemo} from 'react'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import useNetwork from '@hooks/useNetwork'; +import * as Report from '@libs/actions/Report'; +import compose from '@libs/compose'; +import getComponentDisplayName from '@libs/getComponentDisplayName'; +import * as ReportUtils from '@libs/ReportUtils'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import ONYXKEYS from '@src/ONYXKEYS'; +import * as OnyxTypes from '@src/types/onyx'; +import withReportOrNotFound from './withReportOrNotFound'; +import type {WithReportOrNotFoundOnyxProps} from './withReportOrNotFound'; + +type OnyxProps = { + /** Session of currently logged in user */ + session: OnyxEntry; +}; + +type ComponentProps = OnyxProps & + WithReportOrNotFoundOnyxProps & { + route: RouteProp<{params: {reportID: string; accountID: string}}>; + }; + +export default function (WrappedComponent: ComponentType>) { + // eslint-disable-next-line rulesdir/no-negated-variables + function WithReportAndPrivateNotesOrNotFound(props: TProps, ref: ForwardedRef) { + const {isOffline} = useNetwork(); + const {route, report, session} = props; + const accountID = route.params.accountID; + const isPrivateNotesFetchTriggered = !!report?.isLoadingPrivateNotes; + + useEffect(() => { + // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. + if (isPrivateNotesFetchTriggered || !!isOffline || !report?.reportID) { + return; + } + + Report.getReportPrivateNote(report.reportID); + // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies + }, [report?.reportID, isOffline, isPrivateNotesFetchTriggered]); + + const isPrivateNotesEmpty = !!(accountID ? report?.privateNotes?.[Number(accountID)].note : report?.privateNotes); + const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchTriggered || (isPrivateNotesEmpty && !!report?.isLoadingPrivateNotes); + + // eslint-disable-next-line rulesdir/no-negated-variables + const shouldShowNotFoundPage = useMemo(() => { + // Show not found view if the report is archived, or if the note is not of current user. + if (ReportUtils.isArchivedRoom(report) || (accountID && Number(session?.accountID) !== Number(accountID))) { + return true; + } + + // Don't show not found view if the notes are still loading, or if the notes are non-empty. + if (shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty) { + return false; + } + + // As notes being empty and not loading is a valid case, show not found view only in offline mode. + return !!isOffline; + }, [report, isOffline, accountID, session?.accountID, isPrivateNotesEmpty, shouldShowFullScreenLoadingIndicator]); + + if (shouldShowFullScreenLoadingIndicator) { + return ; + } + + if (shouldShowNotFoundPage) { + return ; + } + + return ( + + ); + } + + WithReportAndPrivateNotesOrNotFound.displayName = `withReportAndPrivateNotesOrNotFound(${getComponentDisplayName(WrappedComponent)})`; + + return compose( + withOnyx, OnyxProps>({ + session: { + key: ONYXKEYS.SESSION, + }, + }), + withReportOrNotFound(), + )(forwardRef(WithReportAndPrivateNotesOrNotFound)); +} diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index cf2c0d5aca4b..c03b2047d3df 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -9,7 +9,7 @@ import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import ONYXKEYS from '@src/ONYXKEYS'; import * as OnyxTypes from '@src/types/onyx'; -type OnyxProps = { +type WithReportOrNotFoundOnyxProps = { /** The report currently being looked at */ report: OnyxEntry; /** The policies which the user has access to */ @@ -20,16 +20,16 @@ type OnyxProps = { isLoadingReportData: OnyxEntry; }; -type ComponentProps = OnyxProps & { +type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & { route: RouteProp<{params: {reportID: string}}>; }; export default function ( shouldRequireReportID = true, -): ( +): ( WrappedComponent: React.ComponentType>, -) => React.ComponentType, keyof OnyxProps>> { - return function (WrappedComponent: ComponentType>) { +) => React.ComponentType, keyof WithReportOrNotFoundOnyxProps>> { + return function (WrappedComponent: ComponentType>) { function WithReportOrNotFound(props: TProps, ref: ForwardedRef) { const contentShown = React.useRef(false); @@ -71,7 +71,7 @@ export default function ( WithReportOrNotFound.displayName = `withReportOrNotFound(${getComponentDisplayName(WrappedComponent)})`; - return withOnyx, OnyxProps>({ + return withOnyx, WithReportOrNotFoundOnyxProps>({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, }, @@ -87,3 +87,5 @@ export default function ( })(React.forwardRef(WithReportOrNotFound)); }; } + +export type {WithReportOrNotFoundOnyxProps, WithReportOrNotFoundProps}; From 77ce449bf3a88a7ceffb4507b220c9b2766aa0b9 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 8 Jan 2024 18:30:07 +0000 Subject: [PATCH 02/11] refactor: apply suggestions --- .../withReportAndPrivateNotesOrNotFound.tsx | 45 ++++++++++--------- .../home/report/withReportOrNotFound.tsx | 4 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index 83e4248c81b6..09292afeda42 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,6 +1,6 @@ -import {RouteProp} from '@react-navigation/core'; -import React, {ComponentType, ForwardedRef, forwardRef, RefAttributes, useEffect, useMemo} from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import React, {useEffect, useMemo} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry} from 'react-native-onyx/lib/types'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useNetwork from '@hooks/useNetwork'; import * as Report from '@libs/actions/Report'; @@ -10,39 +10,40 @@ import * as ReportUtils from '@libs/ReportUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import ONYXKEYS from '@src/ONYXKEYS'; import * as OnyxTypes from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withReportOrNotFound from './withReportOrNotFound'; -import type {WithReportOrNotFoundOnyxProps} from './withReportOrNotFound'; +import type {WithReportOrNotFoundOnyxProps, WithReportOrNotFoundProps} from './withReportOrNotFound'; -type OnyxProps = { +type WithReportAndPrivateNotesOrNotFoundOnyxProps = { /** Session of currently logged in user */ session: OnyxEntry; }; -type ComponentProps = OnyxProps & - WithReportOrNotFoundOnyxProps & { - route: RouteProp<{params: {reportID: string; accountID: string}}>; - }; +type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps; -export default function (WrappedComponent: ComponentType>) { +export default function ( + WrappedComponent: React.ComponentType>, +): React.ComponentType, keyof WithReportAndPrivateNotesOrNotFoundOnyxProps>, keyof WithReportOrNotFoundOnyxProps>> { // eslint-disable-next-line rulesdir/no-negated-variables - function WithReportAndPrivateNotesOrNotFound(props: TProps, ref: ForwardedRef) { - const {isOffline} = useNetwork(); + function WithReportAndPrivateNotesOrNotFound(props: TProps, ref: React.ForwardedRef) { const {route, report, session} = props; - const accountID = route.params.accountID; - const isPrivateNotesFetchTriggered = !!report?.isLoadingPrivateNotes; + const accountID = Number(route.params.accountID); + const isPrivateNotesFetchTriggered = report?.isLoadingPrivateNotes !== undefined; + + const {isOffline} = useNetwork(); useEffect(() => { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. - if (isPrivateNotesFetchTriggered || !!isOffline || !report?.reportID) { + if (isPrivateNotesFetchTriggered || isOffline) { return; } - Report.getReportPrivateNote(report.reportID); + Report.getReportPrivateNote(report?.reportID ?? ''); // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies }, [report?.reportID, isOffline, isPrivateNotesFetchTriggered]); - const isPrivateNotesEmpty = !!(accountID ? report?.privateNotes?.[Number(accountID)].note : report?.privateNotes); - const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchTriggered || (isPrivateNotesEmpty && !!report?.isLoadingPrivateNotes); + const isPrivateNotesEmpty = accountID ? !report?.privateNotes?.[accountID]?.note : isEmptyObject(report?.privateNotes); + const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchTriggered || (isPrivateNotesEmpty && report.isLoadingPrivateNotes); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo(() => { @@ -52,12 +53,12 @@ export default function (WrappedComponent: } // Don't show not found view if the notes are still loading, or if the notes are non-empty. - if (shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty) { + if (!!shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty) { return false; } // As notes being empty and not loading is a valid case, show not found view only in offline mode. - return !!isOffline; + return isOffline; }, [report, isOffline, accountID, session?.accountID, isPrivateNotesEmpty, shouldShowFullScreenLoadingIndicator]); if (shouldShowFullScreenLoadingIndicator) { @@ -80,11 +81,11 @@ export default function (WrappedComponent: WithReportAndPrivateNotesOrNotFound.displayName = `withReportAndPrivateNotesOrNotFound(${getComponentDisplayName(WrappedComponent)})`; return compose( - withOnyx, OnyxProps>({ + withOnyx, WithReportAndPrivateNotesOrNotFoundOnyxProps>({ session: { key: ONYXKEYS.SESSION, }, }), withReportOrNotFound(), - )(forwardRef(WithReportAndPrivateNotesOrNotFound)); + )(WithReportAndPrivateNotesOrNotFound); } diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index c03b2047d3df..d224b4522dad 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -21,7 +21,7 @@ type WithReportOrNotFoundOnyxProps = { }; type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & { - route: RouteProp<{params: {reportID: string}}>; + route: RouteProp<{params: {reportID: string; accountID: string}}>; }; export default function ( @@ -88,4 +88,4 @@ export default function ( }; } -export type {WithReportOrNotFoundOnyxProps, WithReportOrNotFoundProps}; +export type {WithReportOrNotFoundProps, WithReportOrNotFoundOnyxProps}; From 66895381b2dea9cf3362682e0a072d14b07e109b Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 9 Jan 2024 15:20:44 +0000 Subject: [PATCH 03/11] chore(typescript): add missing import type --- src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index 09292afeda42..a6987afb46b1 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; -import {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useNetwork from '@hooks/useNetwork'; import * as Report from '@libs/actions/Report'; @@ -9,7 +9,7 @@ import getComponentDisplayName from '@libs/getComponentDisplayName'; import * as ReportUtils from '@libs/ReportUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import ONYXKEYS from '@src/ONYXKEYS'; -import * as OnyxTypes from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withReportOrNotFound from './withReportOrNotFound'; import type {WithReportOrNotFoundOnyxProps, WithReportOrNotFoundProps} from './withReportOrNotFound'; From 3fd6d5d601bdb3b6e8335aa5327fde9b4a21f8dd Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 12 Jan 2024 18:06:07 +0000 Subject: [PATCH 04/11] refactor(typescript): apply pull request feedback --- .../home/report/withReportAndPrivateNotesOrNotFound.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index c04822b786b1..cf5ce669aaaf 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,6 +1,7 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {useEffect, useMemo} from 'react'; -import {type OnyxEntry, withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; @@ -13,6 +14,7 @@ import LoadingPage from '@pages/LoadingPage'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithReportOrNotFoundProps} from './withReportOrNotFound'; import withReportOrNotFound from './withReportOrNotFound'; @@ -36,7 +38,7 @@ export default function { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. From 8c2764a1dda620f5f59080dd1b3c780bee669891 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 30 Jan 2024 17:15:20 +0000 Subject: [PATCH 05/11] chore(typescript): resolve typing errors --- .../PrivateNotes/PrivateNotesEditPage.tsx | 32 ++++++++----------- .../PrivateNotes/PrivateNotesListPage.tsx | 24 +++++--------- .../withReportAndPrivateNotesOrNotFound.tsx | 10 ++++-- .../home/report/withReportOrNotFound.tsx | 16 ++++++---- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index 805f2d8fcb90..efed0363d690 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -1,5 +1,4 @@ import {useFocusEffect} from '@react-navigation/native'; -import type {StackScreenProps} from '@react-navigation/stack'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; @@ -21,13 +20,14 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; +import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import * as ReportActions from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetails, Report} from '@src/types/onyx'; +import type {PersonalDetails} from '@src/types/onyx'; import type {Note} from '@src/types/onyx/Report'; type PrivateNotesEditPageOnyxProps = { @@ -35,11 +35,7 @@ type PrivateNotesEditPageOnyxProps = { personalDetailsList: OnyxCollection; }; -type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & - StackScreenProps & { - /** The report currently being looked at */ - report: Report; - }; +type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & WithReportAndPrivateNotesOrNotFoundProps; function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotesEditPageProps) { const styles = useThemeStyles(); @@ -48,7 +44,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes // We need to edit the note in markdown format, but display it in HTML format const parser = new ExpensiMark(); const [privateNote, setPrivateNote] = useState( - () => ReportActions.getDraftPrivateNote(report.reportID).trim() || parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(), + () => ReportActions.getDraftPrivateNote(report?.reportID ?? '').trim() || parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params?.accountID ?? 0)]?.note ?? '').trim(), ); /** @@ -58,9 +54,9 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes const debouncedSavePrivateNote = useMemo( () => lodashDebounce((text: string) => { - ReportActions.savePrivateNotesDraft(report.reportID, text); + ReportActions.savePrivateNotesDraft(report?.reportID ?? '', text); }, 1000), - [report.reportID], + [report?.reportID], ); // To focus on the input field when the page loads @@ -84,21 +80,21 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes ); const savePrivateNote = () => { - const originalNote = report?.privateNotes?.[Number(route.params.accountID)]?.note ?? ''; + const originalNote = report?.privateNotes?.[Number(route.params?.accountID ?? 0)]?.note ?? ''; let editedNote = ''; if (privateNote.trim() !== originalNote.trim()) { editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim()); - ReportActions.updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote); + ReportActions.updatePrivateNotes(report?.reportID ?? '', Number(route.params?.accountID ?? 0), editedNote); } // We want to delete saved private note draft after saving the note debouncedSavePrivateNote(''); Keyboard.dismiss(); - if (!Object.values({...report.privateNotes, [route.params.accountID]: {note: editedNote}}).some((item) => item.note)) { + if (!Object.values({...report?.privateNotes, [route.params?.accountID ?? 0]: {note: editedNote}}).some((item) => item.note)) { ReportUtils.navigateToDetailsPage(report); } else { - Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID)); + Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report?.reportID ?? '')); } }; @@ -110,7 +106,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes > Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID))} + onBackButtonPress={() => Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report?.reportID ?? ''))} shouldShowBackButton onCloseButtonPress={() => Navigation.dismissModal()} /> @@ -123,16 +119,16 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes > {translate( - Str.extractEmailDomain(personalDetailsList?.[route.params.accountID]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN + Str.extractEmailDomain(personalDetailsList?.[route.params.accountID ?? 0]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN ? 'privateNotes.sharedNoteMessage' : 'privateNotes.personalNoteMessage', )} ReportActions.clearPrivateNotesError(report.reportID, Number(route.params.accountID))} + onClose={() => ReportActions.clearPrivateNotesError(report?.reportID ?? '', Number(route.params?.accountID ?? 0))} style={[styles.mb3]} > ; - - /** Session info for the currently logged in user. */ - session: OnyxEntry; }; -type PrivateNotesListPageProps = PrivateNotesListPageOnyxProps & { - /** The report currently being looked at */ - report: Report; -}; +type PrivateNotesListPageProps = PrivateNotesListPageOnyxProps & WithReportAndPrivateNotesOrNotFoundProps; type NoteListItem = { title: string; @@ -65,12 +60,12 @@ function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNot * Returns a list of private notes on the given chat report */ const privateNotes = useMemo(() => { - const privateNoteBrickRoadIndicator = (accountID: number) => (report.privateNotes?.[accountID].errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined); - return Object.keys(report.privateNotes ?? {}).map((accountID: string) => { - const privateNote = report.privateNotes?.[Number(accountID)]; + const privateNoteBrickRoadIndicator = (accountID: number) => (report?.privateNotes?.[accountID].errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined); + return Object.keys(report?.privateNotes ?? {}).map((accountID: string) => { + const privateNote = report?.privateNotes?.[Number(accountID)]; return { title: Number(session?.accountID) === Number(accountID) ? translate('privateNotes.myNote') : personalDetailsList?.[accountID]?.login ?? '', - action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID)), + action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report?.reportID ?? '', accountID)), brickRoadIndicator: privateNoteBrickRoadIndicator(Number(accountID)), note: privateNote?.note ?? '', disabled: Number(session?.accountID) !== Number(accountID), @@ -101,8 +96,5 @@ export default withReportAndPrivateNotesOrNotFound('privateNotes.title')( personalDetailsList: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, - session: { - key: ONYXKEYS.SESSION, - }, })(PrivateNotesListPage), ); diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index dc40889b23c3..e7757e4b5add 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,3 +1,4 @@ +import type {ParamListBase} from '@react-navigation/native'; import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; @@ -23,7 +24,10 @@ type WithReportAndPrivateNotesOrNotFoundOnyxProps = { session: OnyxEntry; }; -type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps; +type WithReportAndPrivateNotesOrNotFoundProps< + ParamList extends ParamListBase = Record, + RouteName extends string = string, +> = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps; export default function (pageTitle: TranslationPaths) { // eslint-disable-next-line rulesdir/no-negated-variables @@ -33,7 +37,7 @@ export default function ; }; -type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & { - route: RouteProp<{params: {reportID: string; accountID: string}}>; -}; +type WithReportOrNotFoundProps< + ParamList extends ParamListBase = Record, + RouteName extends string = string, +> = WithReportOrNotFoundOnyxProps & StackScreenProps; export default function ( shouldRequireReportID = true, @@ -33,9 +35,9 @@ export default function ( ) => React.ComponentType, keyof WithReportOrNotFoundOnyxProps>> { return function (WrappedComponent: ComponentType>) { function WithReportOrNotFound(props: TProps, ref: ForwardedRef) { - const contentShown = React.useRef(false); + const contentShown = React.useRef(false); - const isReportIdInRoute = props.route.params.reportID?.length; + const isReportIdInRoute = !!props.route.params?.reportID?.length; if (shouldRequireReportID || isReportIdInRoute) { const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.report ?? {}).length || !props.report?.reportID); @@ -75,7 +77,7 @@ export default function ( return withOnyx, WithReportOrNotFoundOnyxProps>({ report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`, }, isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, From 204b07b917a92e9b2b57e1fb099a3b743cf8b917 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 31 Jan 2024 15:51:59 +0000 Subject: [PATCH 06/11] chore(typescript): resolve typing issues --- src/pages/PrivateNotes/PrivateNotesEditPage.tsx | 4 +--- .../home/report/withReportAndPrivateNotesOrNotFound.tsx | 7 ++----- src/pages/home/report/withReportOrNotFound.tsx | 8 ++------ 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index efed0363d690..23c97724d2f4 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -17,7 +17,6 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; @@ -26,7 +25,6 @@ import * as ReportActions from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; import type {PersonalDetails} from '@src/types/onyx'; import type {Note} from '@src/types/onyx/Report'; @@ -35,7 +33,7 @@ type PrivateNotesEditPageOnyxProps = { personalDetailsList: OnyxCollection; }; -type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & WithReportAndPrivateNotesOrNotFoundProps; +type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & WithReportAndPrivateNotesOrNotFoundProps; function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotesEditPageProps) { const styles = useThemeStyles(); diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index e7757e4b5add..09a9a72f2f67 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,4 +1,4 @@ -import type {ParamListBase} from '@react-navigation/native'; +import type {RouteProp} from '@react-navigation/native'; import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; @@ -24,10 +24,7 @@ type WithReportAndPrivateNotesOrNotFoundOnyxProps = { session: OnyxEntry; }; -type WithReportAndPrivateNotesOrNotFoundProps< - ParamList extends ParamListBase = Record, - RouteName extends string = string, -> = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps; +type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps & {route: RouteProp<{params: {accountID?: string}}>}; export default function (pageTitle: TranslationPaths) { // eslint-disable-next-line rulesdir/no-negated-variables diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index 758315ff42cc..48299bd7b9d9 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -1,6 +1,5 @@ /* eslint-disable rulesdir/no-negated-variables */ -import type {ParamListBase} from '@react-navigation/native'; -import type {StackScreenProps} from '@react-navigation/stack'; +import type {RouteProp} from '@react-navigation/native'; import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {useEffect} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -25,10 +24,7 @@ type WithReportOrNotFoundOnyxProps = { isLoadingReportData: OnyxEntry; }; -type WithReportOrNotFoundProps< - ParamList extends ParamListBase = Record, - RouteName extends string = string, -> = WithReportOrNotFoundOnyxProps & StackScreenProps; +type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {route: RouteProp<{params: {reportID?: string}}>}; export default function ( shouldRequireReportID = true, From ca3b14a99f2178eafd86af1d0c4c71b0ca9a4df4 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 1 Feb 2024 12:27:20 +0000 Subject: [PATCH 07/11] chore(typescript): apply pull request feedback --- src/libs/actions/Report.ts | 2 +- src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index c65b2f087b7e..c2342ce4d026 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2374,7 +2374,7 @@ const updatePrivateNotes = (reportID: string, accountID: number, note: string) = }; /** Fetches all the private notes for a given report */ -function getReportPrivateNote(reportID?: string) { +function getReportPrivateNote(reportID: string | undefined) { if (Session.isAnonymousUser()) { return; } diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index 09a9a72f2f67..a4063e9d29e1 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -40,7 +40,7 @@ export default function { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. From cbc0e7b7f9b94fb189af26b2ea3bf399c82ce8bf Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 5 Feb 2024 18:22:20 +0000 Subject: [PATCH 08/11] fix: infinite loading when editing a private note --- src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index a4063e9d29e1..26c506007228 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -40,7 +40,7 @@ export default function { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. From 657dd5fd8917b41d718d6c0d88005f8ad48522ae Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 5 Feb 2024 23:16:06 +0000 Subject: [PATCH 09/11] refactor(typescript): add missing types --- .../PrivateNotes/PrivateNotesEditPage.tsx | 7 ++++++- .../PrivateNotes/PrivateNotesListPage.tsx | 7 ++++++- .../withReportAndPrivateNotesOrNotFound.tsx | 21 +++++++++---------- .../home/report/withReportOrNotFound.tsx | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index 7137d19fa861..910ad7d2943c 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -1,4 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; @@ -18,6 +19,7 @@ import useHtmlPaste from '@hooks/useHtmlPaste'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; @@ -26,6 +28,7 @@ import * as ReportActions from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import type {PersonalDetails} from '@src/types/onyx'; import type {Note} from '@src/types/onyx/Report'; @@ -34,7 +37,9 @@ type PrivateNotesEditPageOnyxProps = { personalDetailsList: OnyxCollection; }; -type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & WithReportAndPrivateNotesOrNotFoundProps; +type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & + WithReportAndPrivateNotesOrNotFoundProps & + StackScreenProps; function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotesEditPageProps) { const styles = useThemeStyles(); diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index 8d8924c031da..c367ee05f0e7 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -1,3 +1,4 @@ +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useMemo} from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -10,11 +11,13 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import type {PersonalDetails} from '@src/types/onyx'; type PrivateNotesListPageOnyxProps = { @@ -22,7 +25,9 @@ type PrivateNotesListPageOnyxProps = { personalDetailsList: OnyxCollection; }; -type PrivateNotesListPageProps = PrivateNotesListPageOnyxProps & WithReportAndPrivateNotesOrNotFoundProps; +type PrivateNotesListPageProps = PrivateNotesListPageOnyxProps & + WithReportAndPrivateNotesOrNotFoundProps & + StackScreenProps; type NoteListItem = { title: string; diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index 26c506007228..94ed29dc6fb6 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,13 +1,12 @@ import type {RouteProp} from '@react-navigation/native'; import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; -import React, {useEffect, useMemo} from 'react'; +import React, {forwardRef, useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import * as Report from '@libs/actions/Report'; -import compose from '@libs/compose'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import * as ReportUtils from '@libs/ReportUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -24,16 +23,17 @@ type WithReportAndPrivateNotesOrNotFoundOnyxProps = { session: OnyxEntry; }; -type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps & {route: RouteProp<{params: {accountID?: string}}>}; +type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & + WithReportOrNotFoundProps & {route: RouteProp, string>}; export default function (pageTitle: TranslationPaths) { // eslint-disable-next-line rulesdir/no-negated-variables return (WrappedComponent: ComponentType>) => { // eslint-disable-next-line rulesdir/no-negated-variables - function WithReportAndPrivateNotesOrNotFound(props: TProps & {forwardedRef: ForwardedRef}) { + function WithReportAndPrivateNotesOrNotFound(props: TProps, ref: ForwardedRef) { const {translate} = useLocalize(); const network = useNetwork(); - const {route, report, session, forwardedRef} = props; + const {route, report, session} = props; const accountID = route.params?.accountID; const isPrivateNotesFetchTriggered = report?.isLoadingPrivateNotes !== undefined; const prevIsOffline = usePrevious(network.isOffline); @@ -82,7 +82,7 @@ export default function ); } @@ -98,14 +98,13 @@ export default function )); - return compose( - withOnyx({ + return withReportOrNotFound()( + withOnyx, WithReportAndPrivateNotesOrNotFoundOnyxProps>({ session: { key: ONYXKEYS.SESSION, }, - }), - withReportOrNotFound(), - )(WithReportAndPrivateNotesOrNotFoundWithRef); + })(forwardRef(WithReportAndPrivateNotesOrNotFoundWithRef)), + ); }; } diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index 4e9aefa30a86..434e1b18adad 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -27,7 +27,7 @@ type WithReportOrNotFoundOnyxProps = { isLoadingReportData: OnyxEntry; }; -type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {route: RouteProp<{params: {reportID?: string}}>}; +type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {route: RouteProp, string>}; export default function ( shouldRequireReportID = true, From cfa56770f669ca64884a1fcc4fbc6a8b60330fa5 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 7 Feb 2024 16:57:05 +0000 Subject: [PATCH 10/11] fix(typescript): route type issues --- .../PrivateNotes/PrivateNotesEditPage.tsx | 33 +++++++------ .../PrivateNotes/PrivateNotesListPage.tsx | 21 ++++----- src/pages/ReportDescriptionPage.tsx | 18 ++----- .../withReportAndPrivateNotesOrNotFound.tsx | 47 ++++++++----------- .../home/report/withReportOrNotFound.tsx | 19 +++++--- 5 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index 910ad7d2943c..aa02dfddd44c 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -29,7 +29,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetails} from '@src/types/onyx'; +import type {PersonalDetails, Report} from '@src/types/onyx'; import type {Note} from '@src/types/onyx/Report'; type PrivateNotesEditPageOnyxProps = { @@ -37,9 +37,12 @@ type PrivateNotesEditPageOnyxProps = { personalDetailsList: OnyxCollection; }; -type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps & - WithReportAndPrivateNotesOrNotFoundProps & - StackScreenProps; +type PrivateNotesEditPageProps = WithReportAndPrivateNotesOrNotFoundProps & + PrivateNotesEditPageOnyxProps & + StackScreenProps & { + /** The report currently being looked at */ + report: Report; + }; function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotesEditPageProps) { const styles = useThemeStyles(); @@ -48,7 +51,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes // We need to edit the note in markdown format, but display it in HTML format const parser = new ExpensiMark(); const [privateNote, setPrivateNote] = useState( - () => ReportActions.getDraftPrivateNote(report?.reportID ?? '').trim() || parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params?.accountID ?? 0)]?.note ?? '').trim(), + () => ReportActions.getDraftPrivateNote(report.reportID).trim() || parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(), ); /** @@ -58,9 +61,9 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes const debouncedSavePrivateNote = useMemo( () => lodashDebounce((text: string) => { - ReportActions.savePrivateNotesDraft(report?.reportID ?? '', text); + ReportActions.savePrivateNotesDraft(report.reportID, text); }, 1000), - [report?.reportID], + [report.reportID], ); // To focus on the input field when the page loads @@ -86,21 +89,21 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes ); const savePrivateNote = () => { - const originalNote = report?.privateNotes?.[Number(route.params?.accountID ?? 0)]?.note ?? ''; + const originalNote = report?.privateNotes?.[Number(route.params.accountID)]?.note ?? ''; let editedNote = ''; if (privateNote.trim() !== originalNote.trim()) { editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim()); - ReportActions.updatePrivateNotes(report?.reportID ?? '', Number(route.params?.accountID ?? 0), editedNote); + ReportActions.updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote); } // We want to delete saved private note draft after saving the note debouncedSavePrivateNote(''); Keyboard.dismiss(); - if (!Object.values({...report?.privateNotes, [route.params?.accountID ?? 0]: {note: editedNote}}).some((item) => item.note)) { + if (!Object.values({...report.privateNotes, [route.params.accountID]: {note: editedNote}}).some((item) => item.note)) { ReportUtils.navigateToDetailsPage(report); } else { - Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report?.reportID ?? '')); + Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID)); } }; @@ -112,7 +115,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes > Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report?.reportID ?? ''))} + onBackButtonPress={() => Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID))} shouldShowBackButton onCloseButtonPress={() => Navigation.dismissModal()} /> @@ -125,16 +128,16 @@ function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotes > {translate( - Str.extractEmailDomain(personalDetailsList?.[route.params.accountID ?? 0]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN + Str.extractEmailDomain(personalDetailsList?.[route.params.accountID]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN ? 'privateNotes.sharedNoteMessage' : 'privateNotes.personalNoteMessage', )} ReportActions.clearPrivateNotesError(report?.reportID ?? '', Number(route.params?.accountID ?? 0))} + onClose={() => ReportActions.clearPrivateNotesError(report.reportID, Number(route.params.accountID))} style={[styles.mb3]} > ; }; -type PrivateNotesListPageProps = PrivateNotesListPageOnyxProps & - WithReportAndPrivateNotesOrNotFoundProps & - StackScreenProps; +type PrivateNotesListPageProps = WithReportAndPrivateNotesOrNotFoundProps & + PrivateNotesListPageOnyxProps & { + /** The report currently being looked at */ + report: Report; + }; type NoteListItem = { title: string; @@ -65,12 +64,12 @@ function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNot * Returns a list of private notes on the given chat report */ const privateNotes = useMemo(() => { - const privateNoteBrickRoadIndicator = (accountID: number) => (report?.privateNotes?.[accountID].errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined); - return Object.keys(report?.privateNotes ?? {}).map((accountID: string) => { - const privateNote = report?.privateNotes?.[Number(accountID)]; + const privateNoteBrickRoadIndicator = (accountID: number) => (report.privateNotes?.[accountID].errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined); + return Object.keys(report.privateNotes ?? {}).map((accountID: string) => { + const privateNote = report.privateNotes?.[Number(accountID)]; return { title: Number(session?.accountID) === Number(accountID) ? translate('privateNotes.myNote') : personalDetailsList?.[accountID]?.login ?? '', - action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report?.reportID ?? '', accountID)), + action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID)), brickRoadIndicator: privateNoteBrickRoadIndicator(Number(accountID)), note: privateNote?.note ?? '', disabled: Number(session?.accountID) !== Number(accountID), diff --git a/src/pages/ReportDescriptionPage.tsx b/src/pages/ReportDescriptionPage.tsx index 3ccabf30c1b7..6062ef748f36 100644 --- a/src/pages/ReportDescriptionPage.tsx +++ b/src/pages/ReportDescriptionPage.tsx @@ -1,22 +1,14 @@ -import type {RouteProp} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; -import type {OnyxCollection} from 'react-native-onyx'; import * as ReportUtils from '@libs/ReportUtils'; -import type * as OnyxTypes from '@src/types/onyx'; +import type {ReportDescriptionNavigatorParamList} from '@navigation/types'; +import type SCREENS from '@src/SCREENS'; +import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import RoomDescriptionPage from './RoomDescriptionPage'; import TaskDescriptionPage from './tasks/TaskDescriptionPage'; -type ReportDescriptionPageProps = { - /** The report currently being looked at */ - report: OnyxTypes.Report; - - /** Policy for the current report */ - policies: OnyxCollection; - - /** Route params */ - route: RouteProp<{params: {reportID: string}}>; -}; +type ReportDescriptionPageProps = WithReportOrNotFoundProps & StackScreenProps; function ReportDescriptionPage(props: ReportDescriptionPageProps) { const isTask = ReportUtils.isTaskReport(props.report); diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index 94ed29dc6fb6..4becf72b5829 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -1,8 +1,7 @@ -import type {RouteProp} from '@react-navigation/native'; +import React, {useEffect, useMemo} from 'react'; import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; -import React, {forwardRef, useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; @@ -15,7 +14,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {WithReportOrNotFoundProps} from './withReportOrNotFound'; +import type {WithReportOrNotFoundOnyxProps, WithReportOrNotFoundProps} from './withReportOrNotFound'; import withReportOrNotFound from './withReportOrNotFound'; type WithReportAndPrivateNotesOrNotFoundOnyxProps = { @@ -23,34 +22,35 @@ type WithReportAndPrivateNotesOrNotFoundOnyxProps = { session: OnyxEntry; }; -type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & - WithReportOrNotFoundProps & {route: RouteProp, string>}; +type WithReportAndPrivateNotesOrNotFoundProps = WithReportAndPrivateNotesOrNotFoundOnyxProps & WithReportOrNotFoundProps; -export default function (pageTitle: TranslationPaths) { +export default function (pageTitle: TranslationPaths) { // eslint-disable-next-line rulesdir/no-negated-variables - return (WrappedComponent: ComponentType>) => { + return ( + WrappedComponent: ComponentType>, + ): React.ComponentType & RefAttributes, keyof WithReportOrNotFoundOnyxProps>> => { // eslint-disable-next-line rulesdir/no-negated-variables function WithReportAndPrivateNotesOrNotFound(props: TProps, ref: ForwardedRef) { const {translate} = useLocalize(); - const network = useNetwork(); + const {isOffline} = useNetwork(); const {route, report, session} = props; - const accountID = route.params?.accountID; - const isPrivateNotesFetchTriggered = report?.isLoadingPrivateNotes !== undefined; - const prevIsOffline = usePrevious(network.isOffline); - const isReconnecting = prevIsOffline && !network.isOffline; + const accountID = 'accountID' in route.params && route.params.accountID; + const isPrivateNotesFetchTriggered = report.isLoadingPrivateNotes !== undefined; + const prevIsOffline = usePrevious(isOffline); + const isReconnecting = prevIsOffline && !isOffline; const isOtherUserNote = accountID && Number(session?.accountID) !== Number(accountID); const isPrivateNotesFetchFinished = isPrivateNotesFetchTriggered && !report.isLoadingPrivateNotes; const isPrivateNotesEmpty = accountID ? !report?.privateNotes?.[Number(accountID)].note : isEmptyObject(report?.privateNotes); useEffect(() => { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. - if ((isPrivateNotesFetchTriggered && !isReconnecting) || network.isOffline) { + if ((isPrivateNotesFetchTriggered && !isReconnecting) || isOffline) { return; } - Report.getReportPrivateNote(report?.reportID); + Report.getReportPrivateNote(report.reportID); // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies - }, [report?.reportID, network.isOffline, isPrivateNotesFetchTriggered, isReconnecting]); + }, [report.reportID, isOffline, isPrivateNotesFetchTriggered, isReconnecting]); const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchFinished || (isPrivateNotesEmpty && (!!report.isLoadingPrivateNotes || !isOtherUserNote)); @@ -67,8 +67,8 @@ export default function ; @@ -89,21 +89,12 @@ export default function ) => ( - - )); - return withReportOrNotFound()( withOnyx, WithReportAndPrivateNotesOrNotFoundOnyxProps>({ session: { key: ONYXKEYS.SESSION, }, - })(forwardRef(WithReportAndPrivateNotesOrNotFoundWithRef)), + })(WithReportAndPrivateNotesOrNotFound), ); }; } diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index 434e1b18adad..462a56149873 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -7,9 +7,11 @@ import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import * as ReportUtils from '@libs/ReportUtils'; +import type {PrivateNotesNavigatorParamList, ReportDescriptionNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -27,7 +29,12 @@ type WithReportOrNotFoundOnyxProps = { isLoadingReportData: OnyxEntry; }; -type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {route: RouteProp, string>}; +type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & { + route: RouteProp | RouteProp; + + /** The report currently being looked at */ + report: OnyxTypes.Report; +}; export default function ( shouldRequireReportID = true, @@ -36,9 +43,9 @@ export default function ( ) => React.ComponentType, keyof WithReportOrNotFoundOnyxProps>> { return function (WrappedComponent: ComponentType>) { function WithReportOrNotFound(props: TProps, ref: ForwardedRef) { - const contentShown = React.useRef(false); + const contentShown = React.useRef(false); - const isReportIdInRoute = !!props.route.params?.reportID?.length; + const isReportIdInRoute = props.route.params.reportID?.length; // When accessing certain report-dependant pages (e.g. Task Title) by deeplink, the OpenReport API is not called, // So we need to call OpenReport API here to make sure the report data is loaded if it exists on the Server @@ -48,9 +55,9 @@ export default function ( return; } - Report.openReport(props.route.params?.reportID ?? ''); + Report.openReport(props.route.params.reportID); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isReportIdInRoute, props.route.params?.reportID]); + }, [isReportIdInRoute, props.route.params.reportID]); if (shouldRequireReportID || isReportIdInRoute) { const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.report ?? {}).length || !props.report?.reportID); @@ -90,7 +97,7 @@ export default function ( return withOnyx, WithReportOrNotFoundOnyxProps>({ report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, }, isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, From 9b6db9430dda9e55f3eba98e544e61f24a6a0391 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 8 Feb 2024 11:06:32 +0000 Subject: [PATCH 11/11] fix: infinite loader on private notes list --- .../report/withReportAndPrivateNotesOrNotFound.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx index 4becf72b5829..d8d461568a45 100644 --- a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.tsx @@ -34,13 +34,13 @@ export default function (pageTitle: TranslationPaths) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); const {route, report, session} = props; - const accountID = 'accountID' in route.params && route.params.accountID; + const accountID = ('accountID' in route.params && route.params.accountID) || ''; const isPrivateNotesFetchTriggered = report.isLoadingPrivateNotes !== undefined; const prevIsOffline = usePrevious(isOffline); const isReconnecting = prevIsOffline && !isOffline; - const isOtherUserNote = accountID && Number(session?.accountID) !== Number(accountID); + const isOtherUserNote = !!accountID && Number(session?.accountID) !== Number(accountID); const isPrivateNotesFetchFinished = isPrivateNotesFetchTriggered && !report.isLoadingPrivateNotes; - const isPrivateNotesEmpty = accountID ? !report?.privateNotes?.[Number(accountID)].note : isEmptyObject(report?.privateNotes); + const isPrivateNotesEmpty = accountID ? !report?.privateNotes?.[Number(accountID)]?.note : isEmptyObject(report?.privateNotes); useEffect(() => { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. @@ -52,12 +52,12 @@ export default function (pageTitle: TranslationPaths) { // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies }, [report.reportID, isOffline, isPrivateNotesFetchTriggered, isReconnecting]); - const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchFinished || (isPrivateNotesEmpty && (!!report.isLoadingPrivateNotes || !isOtherUserNote)); + const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchFinished; // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo(() => { // Show not found view if the report is archived, or if the note is not of current user. - if (ReportUtils.isArchivedRoom(report) || (accountID && Number(session?.accountID) !== Number(accountID))) { + if (ReportUtils.isArchivedRoom(report) || isOtherUserNote) { return true; } @@ -68,7 +68,7 @@ export default function (pageTitle: TranslationPaths) { // As notes being empty and not loading is a valid case, show not found view only in offline mode. return isOffline; - }, [report, isOffline, accountID, session?.accountID, isPrivateNotesEmpty, shouldShowFullScreenLoadingIndicator, isReconnecting]); + }, [report, isOtherUserNote, shouldShowFullScreenLoadingIndicator, isPrivateNotesEmpty, isReconnecting, isOffline]); if (shouldShowFullScreenLoadingIndicator) { return ;