[React-Query] useInfinteQuery 사용시 주의사항 (무한스크롤)

리액트 무한 스크롤 구현

  1. 무한 스크롤 구현방법에는 2가지가 있다.

    1. scroll 이벤트를 통해 페이지 마지막부분으로 scroll이 되었을경우 새로운 page를 fetching하는 방법.

    2. 페이지 마지막 부분에 div태그를 두고 Intersection Observer를 통해 해당 div가 화면에 보일때 새로운 page를 fetching 하는 방법

    두가지 방법의 차이점과 어느게 더 좋은지에 관한것은 다른 블로그에도 많으니 생략하겠다.



  2. 리액트에서 Intersection Observer를 편하게 사용하기 위한 라이브러리가 있다.

    react-intersection-observer를 사용해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import React from 'react';
    import { useInView } from 'react-intersection-observer';
     
    const Component = () => {
      const { ref, inView, entry } = useInView({
        /* Optional options */
        // threshold: 0,
        // rootMargin:"50px"
      });
     
      return (
        <div>
            <ul>
                .......
            </ul>
            <div ref={ref}>
                <h2>{`Header inside viewport ${inView}.`}</h2>
            </div>
        </div>    
      );
    };
    cs

    ref를 페이지 마지막 div에 넣고 해당 div가 뷰포트 안에 들어오면 inView 값이 true가 되는 방식이다.



  3. useInfiniteQuery를 사용해보자.

    이제 inView값이 true 일때 useInfiniteQuery가 다음 page를 Fetching 하게 되면 된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ...
    import { useInfiniteQuery } from "@tanstack/react-query";
     
     
    const Component = () => {
        ....
     
        const { isFetchingNextPage, fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {
            enabled: false// 자동으로 fetch가 되는것을 방지하고 오직 fetchNextPage함수에 의해서만 fetch 되도록 하기 위해
            getNextPageParams: (lastPage, allPages) => lastPage.page < lastPage.totalPage ? lastPage.page + 1 : undefined,
            // 마지막 페이지가 아닐경우 다음 페이지 넘버를 반환하고 마지막 페이지일 경우 undefined를 반환한다.
        });    
     
        useEffect(() => {
            if (inView && !isFetchingNextPage) {
                fetchNextPage()
            }
        }, [inView, isFetchingNextPage])
     
        return ...
    }
    cs

    fetchFn에 pageParam이 전달된다.

    1
    2
    3
    4
    const fetchFn = ({ pageParam = 1}) => {
        return fetch(`/api/getsomething?page=${pageParam}`)
            .then(response => response.json())
    }
    cs

    하지만 가장처음 호출될때는 undefined가 전달된다. 한번도 호출된적이 없기 때문에 getNextPageParam에서 사용될 lastPage가 없기 때문이다. 그래서 default값으로 1을 넣어준다. 참고

    이 방법에는 문제점이 있다.

    실제로 fetchNextPage가 실행되는 순간 isFetchingNextPage의 값이 true가 되기전에 바로 fetchNextPage가 또한번 더 실행된다. fetchNextPage가 비동기함수로써 실행되기까지 시간이 걸리기 때문이다. (isFetchingNextPage 값이 true가 되기까지 시간이 걸린다. 그전에 또다시 fetchNextPage가 실행된다)



  4. useState를 통해 상태를 관리해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    ...
    import { useState} from 'react';
    import { useInfiniteQuery } from "@tanstack/react-query";
     
     
    const Component = () => {
        ....
        const [data, setData] = useState([]);
     
        const [isLoading, setIsLoading] = useState(false);
     
        const { fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {
            ... 
            onSuccess: (newData) => {
                setData(newData.pages.map(pages.results).flat());
                setIsLoading(false);    // 다시 fetchNextPage가 가능할 수 있도록 해준다.
            }
        });    
     
        useEffect(() => {
            if (inView && !isLoading) {
                setIsLoading(true);
                fetchNextPage();
            }
        }, [inView, isLoading])
     
        return ...
    }
    cs

    위의 경우에는 fetchNextPage가 동시에 여러번 수행되지는 않는다. 하지만 fetch가 끝나고나서 setIsLoading(false)가 호출되는 순간 실제로 화면에 다시 렌더링되면서 화면에 새로운 li 엘레멘트들이 paint되기전에 inView가 트리거되어 또한번 fetchNextPage가 호출된다. 즉, 의도치 않은 fetching이 또 일어난다.



  5. useRef를 통해 상태관리를 해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    ...
    import { useState} from 'react';
    import { useInfiniteQuery } from "@tanstack/react-query";
     
     
    const Component = () => {
        ....
     
        const isLoading = useRef(false);
     
        const { fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {
            ... 
            onSuccess: (newData) => {
                ...
                isLoading.current = false;    // 다시 fetchNextPage가 가능할 수 있도록 해준다.
            }
        });    
     
        useEffect(() => {
            if (inView && !isLoading.current) {
                isLoading.current = true;
                fetchNextPage();
            }
        }, [inView, isLoading])
     
        return ...
    }
    cs

    이제 깔끔하다. 원하는대로 infinite scroll이 구현되었다.

No comments:

Post a Comment