Component with "useIonViewWillEnter" rendering multiple times

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;

2 Likes