I am building a page component for lists, every time it returns to the view I need to refresh the data. I tried using ionViewWillEnter, but the hook is called to manny times.
The component:
import React, { useCallback, useEffect, useState } from 'react';
import {
IonButton,
IonContent,
IonFab,
IonFabButton,
IonIcon,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonItem,
IonLabel,
IonList,
IonPage,
IonTitle,
useIonViewWillEnter,
} from '@ionic/react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { addOutline, addSharp, trashOutline, trashSharp } from 'ionicons/icons';
import { DefaultListSkeleton } from '../Skeletons/DefaultListSkeleton';
import { useToast } from '../../contexts/toast';
import Header from '../Header';
import Searchbar from '../Searchbar';
import Alert from '../DeleteAlert';
import { IResult } from '../../interfaces/IResult';
import { IGetCollectionResponse } from '../../interfaces/backend/responses/IGetCollectionResponse';
interface Props {
title: string;
objName: string;
hasPagination?: boolean;
searchParams?: Record<string, unknown>;
route?: string;
objService: {
GetAll: () => Promise<IResult<IGetCollectionResponse<any>>>;
Search: <T>(params: T) => Promise<IResult<IGetCollectionResponse<any>>>;
Delete: (id: string) => Promise<boolean>;
baseUrl: string;
};
}
const ListPage: React.FC<Props> = ({
title,
objName,
objService,
hasPagination = false,
route,
searchParams,
}) => {
const [list, setList] = useState<any[]>([]);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
const [alert, setAlert] = useState(false);
const [idToDelete, setIdToDelete] = useState('');
const [baseUrl] = useState(route || objService.baseUrl.replace(/_/g, '-'));
const { push } = useHistory();
const [disableInfiniteScroll, setDisableInfiniteScroll] = useState(true);
const { addToast } = useToast();
const { t } = useTranslation();
const fetchDataWithPagination = useCallback(async () => {
try {
let response;
if (searchParams) {
response = await objService.Search({ ...searchParams, page });
} else {
response = await objService.Search({ page });
}
const { data } = response;
if (data && data.items.length > 0) {
if (page === 0) setList(data.items);
else
setList(values => (values ? [...values, ...data.items] : data.items));
setDisableInfiniteScroll(data.items.length < 20);
} else {
setDisableInfiniteScroll(true);
}
} catch {
addToast({
color: 'danger',
message: t('Failed to fetch data, check your connection and try again'),
});
}
}, [addToast, objService, page, searchParams, t]);
const fetchData = useCallback(async () => {
try {
let response;
if (searchParams) {
response = await objService.Search(searchParams);
} else {
response = await objService.GetAll();
}
const data = response.data.items;
setList(data);
} catch {
addToast({
color: 'danger',
message: t('Failed to fetch data, check your connection and try again'),
});
}
}, [addToast, objService, searchParams, t]);
const handleItemDelete = async () => {
try {
await objService.Delete(`${idToDelete}`);
const persisted = list?.filter(item => item.id !== idToDelete);
setList(persisted);
addToast({
color: 'success',
message: t(`${objName} successfully deleted`),
});
} catch {
addToast({
color: 'danger',
message: t(
'Failed to delete item, check your connection and try again',
),
});
}
};
useIonViewWillEnter(() => {
console.log('entered');
});
useEffect(() => {
setLoading(true);
setList([]);
if (hasPagination) {
setPage(0);
setDisableInfiniteScroll(false);
fetchDataWithPagination();
} else {
fetchData();
}
setLoading(false);
}, [fetchData, fetchDataWithPagination, hasPagination]);
useEffect(() => {
if (hasPagination) fetchDataWithPagination();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page]);
return (
<IonPage>
<Header title={t(title)} />
<IonContent scrollY className="app">
<div className="new-button-container" slot="fixed">
<Searchbar list={list} setList={setList} />
<IonButton
data-testid={`new-${baseUrl}-button`}
onClick={() => push(`form`)}
>
{t(`Add ${objName.toLowerCase()}`)}
</IonButton>
</div>
<div className="default-list-container">
{loading ? (
<DefaultListSkeleton />
) : (
<IonList>
{list.length === 0 ? (
<IonTitle>{t('There are no records')}</IonTitle>
) : (
list.map(item => (
<IonItem
button
data-testid={`ion-item-${item.id}`}
key={item.id}
id={item.id || ''}
onClick={() => push(`form/${item.id}`)}
>
<IonLabel>{item.fantasyName || item.name}</IonLabel>
<IonButton
shape="round"
fill="clear"
slot="end"
onClick={e => {
e.stopPropagation();
setIdToDelete(item.id);
setAlert(true);
}}
>
<IonIcon
color="medium"
slot="icon-only"
ios={trashOutline}
md={trashSharp}
/>
</IonButton>
</IonItem>
))
)}
</IonList>
)}
<IonInfiniteScroll
threshold="100px"
disabled={disableInfiniteScroll}
onIonInfinite={(e: CustomEvent<void>) => {
setPage(previousPage => previousPage + 1);
(e.target as HTMLIonInfiniteScrollElement).complete();
}}
>
<IonInfiniteScrollContent
loadingSpinner="circular"
loadingText={t('Loading')}
/>
</IonInfiniteScroll>
</div>
<IonFab
slot="fixed"
className="new-fab-button"
vertical="bottom"
horizontal="end"
>
<IonFabButton
data-testid={`new-${baseUrl}-fab-button`}
onClick={() => push(`form`)}
>
<IonIcon ios={addOutline} md={addSharp} />
</IonFabButton>
</IonFab>
<Alert
message={t(
`Are you sure you want to delete this ${objName.toLowerCase()}?`,
)}
isOpen={alert}
setIsOpen={setAlert}
responseFunction={handleItemDelete}
/>
</IonContent>
</IonPage>
);
};
export default ListPage;
Where iām using it:
import React from 'react';
import CompanyService from '../../../services/Entities/CompaniesService';
import ListPage from '../../../components/ListPage';
const Companies: React.FC = () => {
return (
<ListPage
title="Companies"
objName="Company"
objService={CompanyService}
key="companies"
/>
);
};
export default Companies;