Skip to content

Commit

Permalink
Add hook for infinite scroll JC-720
Browse files Browse the repository at this point in the history
  • Loading branch information
TebyakinaEkaterina committed Aug 21, 2024
1 parent 5c0f2cd commit 1ee153f
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 24 deletions.
39 changes: 15 additions & 24 deletions apps/react/src/features/anime/components/AnimeList/AnimeList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, FC, useCallback, useRef, useEffect } from 'react';
import { memo, FC, useEffect } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { List, ListItem, IconButton } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
Expand All @@ -7,6 +7,7 @@ import { AnimeSortMapper } from '@js-camp/react/api/mappers/animeSortMapper';
import { AnimeTypeMapper } from '@js-camp/core/mappers/anime-type.mapper';
import { useAppDispatch, useAppSelector } from '@js-camp/react/store/store';
import {
selectAdditionalAnimeLoading,
selectAllAnime,
selectAnimeListError,
selectAnimeListLoading,
Expand All @@ -19,17 +20,19 @@ import {
SORTING_SETTINGS_QUERY_PARAM,
} from '@js-camp/react/api/constants';

import { useInfiniteScroll } from '@js-camp/react/features/hooks/useInfiniteScroll';

import { ANIME_PATH } from '../../routes';

import styles from './AnimeList.module.css';

/** Anime list component. */
const AnimeListComponent: FC = () => {
const observer = useRef<IntersectionObserver | null>();
const dispatch = useAppDispatch();
const [searchParams] = useSearchParams();
const animeList = useAppSelector(selectAllAnime);
const isLoading = useAppSelector(selectAnimeListLoading);
const isAdditionalLoading = useAppSelector(selectAdditionalAnimeLoading);
const error = useAppSelector(selectAnimeListError);
const nextPageUrl = useAppSelector(selectNextPageUrl);

Expand All @@ -39,33 +42,20 @@ const AnimeListComponent: FC = () => {
selectedTypes: AnimeTypeMapper.stringToArray(searchParams.get(SELECTED_TYPES_QUERY_PARAM) ?? ''),
sortingSettings: AnimeSortMapper.fromString(searchParams.get(SORTING_SETTINGS_QUERY_PARAM) ?? ''),
};

if (!isLoading) {
if (!isLoading && !isAdditionalLoading) {
dispatch(fetchList(filterParams));
}
}, [searchParams]);

const lastPostElementRef = useCallback(
(node: HTMLAnchorElement) => {
if (isLoading) {
return;
}
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
if (!isLoading && nextPageUrl) {
dispatch(fetchNewPage(nextPageUrl));
}
}
});
if (node) {
observer.current.observe(node);
const lastElementRef = useInfiniteScroll({
isLoading: isLoading && isAdditionalLoading,
nextPageUrl,
fetchData() {
if (nextPageUrl) {
dispatch(fetchNewPage(nextPageUrl));
}
},
[isLoading],
);
});

if (isLoading) {
return <Progress />;
Expand All @@ -90,7 +80,7 @@ const AnimeListComponent: FC = () => {
}
component={Link}
to={`/${ANIME_PATH}/${anime.id}`}
ref={animeList.length === index + 1 ? lastPostElementRef : null}
ref={animeList.length === index + 1 ? lastElementRef : null}
>
<div>
<img
Expand Down Expand Up @@ -140,6 +130,7 @@ const AnimeListComponent: FC = () => {
</p>
</div>
</ListItem>) }
{ isAdditionalLoading ? <Progress /> : null}
</List>
);
};
Expand Down
45 changes: 45 additions & 0 deletions apps/react/src/features/hooks/useInfiniteScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useCallback, useRef } from 'react';

type UseInfiniteScrollProps = {

/** Loading status. */
isLoading: boolean;

/** The URL of the next page of data. */
nextPageUrl: string | null;

/** Function for fetching data. */
fetchData: () => void;
};

/**
* Hook for handle scroll and fetch data.
* @param infiniteScrollProps - Object with params for fetch.
*/
export const useInfiniteScroll = ({ isLoading, nextPageUrl, fetchData }: UseInfiniteScrollProps) => {
const observer = useRef<IntersectionObserver | null>();

const lastElementRef = useCallback(
(node: HTMLAnchorElement) => {
if (isLoading) {
return;
}
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
if (!isLoading && nextPageUrl) {
fetchData();
}
}
});
if (node) {
observer.current.observe(node);
}
},
[isLoading, nextPageUrl],
);

return lastElementRef;
};

0 comments on commit 1ee153f

Please sign in to comment.