diff --git a/src/components/withNavigationTransitionEnd.tsx b/src/components/withNavigationTransitionEnd.tsx new file mode 100644 index 000000000000..417d8828c1e4 --- /dev/null +++ b/src/components/withNavigationTransitionEnd.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 WithNavigationTransitionEndProps = {didScreenTransitionEnd: boolean}; + +export default function (WrappedComponent: ComponentType>): React.ComponentType> { + function WithNavigationTransitionEnd(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 ( + + ); + } + + WithNavigationTransitionEnd.displayName = `WithNavigationTransitionEnd(${getComponentDisplayName(WrappedComponent)})`; + + return React.forwardRef(WithNavigationTransitionEnd); +} + +export type {WithNavigationTransitionEndProps}; diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index bb3e928cb47f..77b5c48d8a72 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 withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; +import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; 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 & WithNavigationTransitionEndProps; 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 = []; @@ -260,10 +245,12 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa RoomInvitePage.displayName = 'RoomInvitePage'; -export default withReportOrNotFound()( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - })(RoomInvitePage), +export default withNavigationTransitionEnd( + withReportOrNotFound()( + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + })(RoomInvitePage), + ), ); diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 0a922321766e..bb199ddc905f 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 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'; @@ -41,7 +43,7 @@ type UseOptions = { reports: OnyxCollection; }; -type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps; +type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps & WithNavigationTransitionEndProps; 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(); @@ -206,26 +208,24 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro includeSafeAreaPaddingBottom={false} testID={TaskAssigneeSelectorModal.displayName} > - {({didScreenTransitionEnd}) => ( - - + + + - - - - - )} + + ); } @@ -241,4 +241,4 @@ const TaskAssigneeSelectorModalWithOnyx = withOnyx; }; -type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps; +type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & + WithNavigationTransitionEndProps & + 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]); @@ -336,16 +327,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 withNavigationTransitionEnd( + 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), + ), );