Skip to content

Commit

Permalink
Merge pull request #39223 from bernhardoj/fix/22508-infinite-loading-…
Browse files Browse the repository at this point in the history
…invite-page-2

Fix infinite loading on invite and task assignee page after refresh
  • Loading branch information
rlinoz authored Apr 1, 2024
2 parents c848cb6 + 471895f commit aacfa8c
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 76 deletions.
39 changes: 39 additions & 0 deletions src/components/withNavigationTransitionEnd.tsx
Original file line number Diff line number Diff line change
@@ -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 <TProps, TRef>(WrappedComponent: ComponentType<TProps & RefAttributes<TRef>>): React.ComponentType<TProps & RefAttributes<TRef>> {
function WithNavigationTransitionEnd(props: TProps, ref: ForwardedRef<TRef>) {
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();

useEffect(() => {
const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => {
setDidScreenTransitionEnd(true);
});

return unsubscribeTransitionEnd;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<WrappedComponent
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
didScreenTransitionEnd={didScreenTransitionEnd}
ref={ref}
/>
);
}

WithNavigationTransitionEnd.displayName = `WithNavigationTransitionEnd(${getComponentDisplayName(WrappedComponent)})`;

return React.forwardRef(WithNavigationTransitionEnd);
}

export type {WithNavigationTransitionEndProps};
37 changes: 12 additions & 25 deletions src/pages/RoomInvitePage.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -39,19 +38,17 @@ type RoomInvitePageOnyxProps = {
personalDetails: OnyxEntry<PersonalDetailsList>;
};

type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps;
type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps & WithNavigationTransitionEndProps;

type Sections = Array<SectionListData<OptionsListUtils.MemberForList, Section<OptionsListUtils.MemberForList>>>;

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<ReportUtils.OptionData[]>([]);
const [invitePersonalDetails, setInvitePersonalDetails] = useState<ReportUtils.OptionData[]>([]);
const [userToInvite, setUserToInvite] = useState<ReportUtils.OptionData | null>(null);
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
const navigation: StackNavigationProp<RootStackParamList> = useNavigation();

useEffect(() => {
setSearchTerm(SearchInputManager.searchInput);
Expand Down Expand Up @@ -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 = [];

Expand Down Expand Up @@ -260,10 +245,12 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa

RoomInvitePage.displayName = 'RoomInvitePage';

export default withReportOrNotFound()(
withOnyx<RoomInvitePageProps, RoomInvitePageOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
})(RoomInvitePage),
export default withNavigationTransitionEnd(
withReportOrNotFound()(
withOnyx<RoomInvitePageProps, RoomInvitePageOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
})(RoomInvitePage),
),
);
44 changes: 22 additions & 22 deletions src/pages/tasks/TaskAssigneeSelectorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -41,7 +43,7 @@ type UseOptions = {
reports: OnyxCollection<Report>;
};

type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps;
type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps & WithNavigationTransitionEndProps;

function useOptions({reports}: UseOptions) {
const allPersonalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
Expand Down Expand Up @@ -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<RouteProp<TaskDetailsNavigatorParamList, typeof SCREENS.TASK.ASSIGNEE>>();
const {translate} = useLocalize();
Expand Down Expand Up @@ -206,26 +208,24 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro
includeSafeAreaPaddingBottom={false}
testID={TaskAssigneeSelectorModal.displayName}
>
{({didScreenTransitionEnd}) => (
<FullPageNotFoundView shouldShow={isTaskNonEditable}>
<HeaderWithBackButton
title={translate('task.assignee')}
onBackButtonPress={handleBackButtonPress}
<FullPageNotFoundView shouldShow={isTaskNonEditable}>
<HeaderWithBackButton
title={translate('task.assignee')}
onBackButtonPress={handleBackButtonPress}
/>
<View style={[styles.flex1, styles.w100, styles.pRelative]}>
<SelectionList
sections={didScreenTransitionEnd && !isLoading ? sections : []}
ListItem={UserListItem}
onSelectRow={selectReport}
onChangeText={onChangeText}
textInputValue={searchValue}
headerMessage={headerMessage}
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
showLoadingPlaceholder={isLoading || !didScreenTransitionEnd}
/>
<View style={[styles.flex1, styles.w100, styles.pRelative]}>
<SelectionList
sections={didScreenTransitionEnd && !isLoading ? sections : []}
ListItem={UserListItem}
onSelectRow={selectReport}
onChangeText={onChangeText}
textInputValue={searchValue}
headerMessage={headerMessage}
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
showLoadingPlaceholder={isLoading || !didScreenTransitionEnd}
/>
</View>
</FullPageNotFoundView>
)}
</View>
</FullPageNotFoundView>
</ScreenWrapper>
);
}
Expand All @@ -241,4 +241,4 @@ const TaskAssigneeSelectorModalWithOnyx = withOnyx<TaskAssigneeSelectorModalProp
},
})(TaskAssigneeSelectorModal);

export default withCurrentUserPersonalDetails(TaskAssigneeSelectorModalWithOnyx);
export default withNavigationTransitionEnd(withCurrentUserPersonalDetails(TaskAssigneeSelectorModalWithOnyx));
51 changes: 22 additions & 29 deletions src/pages/workspace/WorkspaceInvitePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useNavigation} from '@react-navigation/native';
import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect, useMemo, useState} from 'react';
import type {SectionListData} from 'react-native';
import {View} from 'react-native';
Expand All @@ -12,6 +11,8 @@ 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 useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -49,7 +50,10 @@ type WorkspaceInvitePageOnyxProps = {
invitedEmailsToAccountIDsDraft: OnyxEntry<InvitedEmailsToAccountIDs>;
};

type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.INVITE>;
type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps &
WithNavigationTransitionEndProps &
WorkspaceInvitePageOnyxProps &
StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.INVITE>;

function WorkspaceInvitePage({
route,
Expand All @@ -59,15 +63,14 @@ function WorkspaceInvitePage({
invitedEmailsToAccountIDsDraft,
policy,
isLoadingReportData = true,
didScreenTransitionEnd,
}: WorkspaceInvitePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [searchTerm, setSearchTerm] = useState('');
const [selectedOptions, setSelectedOptions] = useState<MemberForList[]>([]);
const [personalDetails, setPersonalDetails] = useState<OptionData[]>([]);
const [usersToInvite, setUsersToInvite] = useState<OptionData[]>([]);
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
const navigation = useNavigation<StackNavigationProp<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.INVITE>>();
const openWorkspaceInvitePage = () => {
const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetailsProp);
Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs));
Expand All @@ -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]);
Expand Down Expand Up @@ -336,16 +327,18 @@ function WorkspaceInvitePage({

WorkspaceInvitePage.displayName = 'WorkspaceInvitePage';

export default withPolicyAndFullscreenLoading(
withOnyx<WorkspaceInvitePageProps, WorkspaceInvitePageOnyxProps>({
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<WorkspaceInvitePageProps, WorkspaceInvitePageOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
betas: {
key: ONYXKEYS.BETAS,
},
invitedEmailsToAccountIDsDraft: {
key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`,
},
})(WorkspaceInvitePage),
),
);

0 comments on commit aacfa8c

Please sign in to comment.