From a32c7ee49b9fe7f524f552a4b8d3dd3e35d01097 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 28 Mar 2024 18:50:52 +0800 Subject: [PATCH 1/4] create withTransitionEnd hook --- src/components/withTransitionEnd.tsx | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/components/withTransitionEnd.tsx diff --git a/src/components/withTransitionEnd.tsx b/src/components/withTransitionEnd.tsx new file mode 100644 index 000000000000..0047b60efcc5 --- /dev/null +++ b/src/components/withTransitionEnd.tsx @@ -0,0 +1,39 @@ +import {useNavigation} from '@react-navigation/native'; +import type {StackNavigationProp} from '@react-navigation/stack'; +import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; +import React, {useEffect, useState} from 'react'; +import getComponentDisplayName from '@libs/getComponentDisplayName'; +import type {RootStackParamList} from '@libs/Navigation/types'; + +type WithTransitionEndProps = {didScreenTransitionEnd: boolean}; + +export default function (WrappedComponent: ComponentType>): React.ComponentType> { + function WithTransitionEnd(props: TProps, ref: ForwardedRef) { + const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); + const navigation = useNavigation>(); + + useEffect(() => { + const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => { + setDidScreenTransitionEnd(true); + }); + + return unsubscribeTransitionEnd; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); + } + + WithTransitionEnd.displayName = `WithTransitionEnd(${getComponentDisplayName(WrappedComponent)})`; + + return WithTransitionEnd; +} + +export type {WithTransitionEndProps}; From bb40f856185258c5fc16561754d28ae6ebcef8aa Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 28 Mar 2024 18:51:09 +0800 Subject: [PATCH 2/4] use didScreenTransitionEnd from withTransitionEnd HOC --- src/pages/RoomInvitePage.tsx | 37 +++++--------- src/pages/tasks/TaskAssigneeSelectorModal.tsx | 44 ++++++++-------- src/pages/workspace/WorkspaceInvitePage.tsx | 51 ++++++++----------- 3 files changed, 56 insertions(+), 76 deletions(-) diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index d0ea55a923e7..35c4759cdaad 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -1,5 +1,3 @@ -import {useNavigation} from '@react-navigation/native'; -import type {StackNavigationProp} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; @@ -13,12 +11,13 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import type {Section} from '@components/SelectionList/types'; import UserListItem from '@components/SelectionList/UserListItem'; +import withTransitionEnd from '@components/withTransitionEnd'; +import type {WithTransitionEndProps} from '@components/withTransitionEnd'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; -import type {RootStackParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; @@ -39,19 +38,17 @@ type RoomInvitePageOnyxProps = { personalDetails: OnyxEntry; }; -type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps; +type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps & WithTransitionEndProps; type Sections = Array>>; -function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePageProps) { +function RoomInvitePage({betas, personalDetails, report, policies, didScreenTransitionEnd}: RoomInvitePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchTerm, setSearchTerm] = useState(''); const [selectedOptions, setSelectedOptions] = useState([]); const [invitePersonalDetails, setInvitePersonalDetails] = useState([]); const [userToInvite, setUserToInvite] = useState(null); - const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); - const navigation: StackNavigationProp = useNavigation(); useEffect(() => { setSearchTerm(SearchInputManager.searchInput); @@ -88,18 +85,6 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change }, [personalDetails, betas, searchTerm, excludedUsers]); - useEffect(() => { - const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => { - setDidScreenTransitionEnd(true); - }); - - return () => { - unsubscribeTransitionEnd(); - }; - // Rule disabled because this effect is only for component did mount & will component unmount lifecycle event - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const sections = useMemo(() => { const sectionsArr: Sections = []; let indexOffset = 0; @@ -266,10 +251,12 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa RoomInvitePage.displayName = 'RoomInvitePage'; -export default withReportOrNotFound()( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - })(RoomInvitePage), +export default withTransitionEnd( + withReportOrNotFound()( + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + })(RoomInvitePage), + ), ); diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 0ffb33b7590b..a8ae93dd53c9 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -14,6 +14,8 @@ import type {ListItem} from '@components/SelectionList/types'; import UserListItem from '@components/SelectionList/UserListItem'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withTransitionEnd from '@components/withTransitionEnd'; +import type {WithTransitionEndProps} from '@components/withTransitionEnd'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; @@ -41,7 +43,7 @@ type UseOptions = { reports: OnyxCollection; }; -type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps; +type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps & WithTransitionEndProps; function useOptions({reports}: UseOptions) { const allPersonalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; @@ -90,7 +92,7 @@ function useOptions({reports}: UseOptions) { return {...options, isLoading, searchValue, debouncedSearchValue, setSearchValue}; } -function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalProps) { +function TaskAssigneeSelectorModal({reports, task, didScreenTransitionEnd}: TaskAssigneeSelectorModalProps) { const styles = useThemeStyles(); const route = useRoute>(); const {translate} = useLocalize(); @@ -212,26 +214,24 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro includeSafeAreaPaddingBottom={false} testID={TaskAssigneeSelectorModal.displayName} > - {({didScreenTransitionEnd}) => ( - - + + + - - - - - )} + + ); } @@ -247,4 +247,4 @@ const TaskAssigneeSelectorModalWithOnyx = withOnyx; }; -type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps; +type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & + WithTransitionEndProps & + WorkspaceInvitePageOnyxProps & + StackScreenProps; function WorkspaceInvitePage({ route, @@ -59,6 +63,7 @@ function WorkspaceInvitePage({ invitedEmailsToAccountIDsDraft, policy, isLoadingReportData = true, + didScreenTransitionEnd, }: WorkspaceInvitePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -66,8 +71,6 @@ function WorkspaceInvitePage({ const [selectedOptions, setSelectedOptions] = useState([]); const [personalDetails, setPersonalDetails] = useState([]); const [usersToInvite, setUsersToInvite] = useState([]); - const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); - const navigation = useNavigation>(); const openWorkspaceInvitePage = () => { const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetailsProp); Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs)); @@ -86,18 +89,6 @@ function WorkspaceInvitePage({ // eslint-disable-next-line react-hooks/exhaustive-deps -- policyID changes remount the component }, []); - useEffect(() => { - const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => { - setDidScreenTransitionEnd(true); - }); - - return () => { - unsubscribeTransitionEnd(); - }; - // Rule disabled because this effect is only for component did mount & will component unmount lifecycle event - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useNetwork({onReconnect: openWorkspaceInvitePage}); const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policyMembers, personalDetailsProp), [policyMembers, personalDetailsProp]); @@ -342,16 +333,18 @@ function WorkspaceInvitePage({ WorkspaceInvitePage.displayName = 'WorkspaceInvitePage'; -export default withPolicyAndFullscreenLoading( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - invitedEmailsToAccountIDsDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, - }, - })(WorkspaceInvitePage), +export default withTransitionEnd( + withPolicyAndFullscreenLoading( + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + invitedEmailsToAccountIDsDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, + }, + })(WorkspaceInvitePage), + ), ); From 77c05c9b02ebbda60f78786afab7b9ffb9832b29 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 28 Mar 2024 19:01:07 +0800 Subject: [PATCH 3/4] add missing forwardRef --- src/components/withTransitionEnd.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/withTransitionEnd.tsx b/src/components/withTransitionEnd.tsx index 0047b60efcc5..b00e0a9f4f34 100644 --- a/src/components/withTransitionEnd.tsx +++ b/src/components/withTransitionEnd.tsx @@ -33,7 +33,7 @@ export default function (WrappedComponent: ComponentType Date: Sat, 30 Mar 2024 13:41:28 +0800 Subject: [PATCH 4/4] renaming --- ...ansitionEnd.tsx => withNavigationTransitionEnd.tsx} | 10 +++++----- src/pages/RoomInvitePage.tsx | 8 ++++---- src/pages/tasks/TaskAssigneeSelectorModal.tsx | 8 ++++---- src/pages/workspace/WorkspaceInvitePage.tsx | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) rename src/components/{withTransitionEnd.tsx => withNavigationTransitionEnd.tsx} (76%) diff --git a/src/components/withTransitionEnd.tsx b/src/components/withNavigationTransitionEnd.tsx similarity index 76% rename from src/components/withTransitionEnd.tsx rename to src/components/withNavigationTransitionEnd.tsx index b00e0a9f4f34..417d8828c1e4 100644 --- a/src/components/withTransitionEnd.tsx +++ b/src/components/withNavigationTransitionEnd.tsx @@ -5,10 +5,10 @@ import React, {useEffect, useState} from 'react'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import type {RootStackParamList} from '@libs/Navigation/types'; -type WithTransitionEndProps = {didScreenTransitionEnd: boolean}; +type WithNavigationTransitionEndProps = {didScreenTransitionEnd: boolean}; export default function (WrappedComponent: ComponentType>): React.ComponentType> { - function WithTransitionEnd(props: TProps, ref: ForwardedRef) { + function WithNavigationTransitionEnd(props: TProps, ref: ForwardedRef) { const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const navigation = useNavigation>(); @@ -31,9 +31,9 @@ export default function (WrappedComponent: ComponentType; }; -type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps & WithTransitionEndProps; +type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps & WithNavigationTransitionEndProps; type Sections = Array>>; @@ -245,7 +245,7 @@ function RoomInvitePage({betas, personalDetails, report, policies, didScreenTran RoomInvitePage.displayName = 'RoomInvitePage'; -export default withTransitionEnd( +export default withNavigationTransitionEnd( withReportOrNotFound()( withOnyx({ personalDetails: { diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index f293c5ff03c4..bb199ddc905f 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -14,8 +14,8 @@ import type {ListItem} from '@components/SelectionList/types'; import UserListItem from '@components/SelectionList/UserListItem'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withTransitionEnd from '@components/withTransitionEnd'; -import type {WithTransitionEndProps} from '@components/withTransitionEnd'; +import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; +import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; @@ -43,7 +43,7 @@ type UseOptions = { reports: OnyxCollection; }; -type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps & WithTransitionEndProps; +type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps & WithNavigationTransitionEndProps; function useOptions({reports}: UseOptions) { const allPersonalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; @@ -241,4 +241,4 @@ const TaskAssigneeSelectorModalWithOnyx = withOnyx; @@ -327,7 +327,7 @@ function WorkspaceInvitePage({ WorkspaceInvitePage.displayName = 'WorkspaceInvitePage'; -export default withTransitionEnd( +export default withNavigationTransitionEnd( withPolicyAndFullscreenLoading( withOnyx({ personalDetails: {