본문 바로가기
SPA/React

[React] React Query의 useInfiniteQuery에 대해 알아보기

by J4J 2022. 3. 13.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 React Query의 useInfiniteQuery에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

들어가기에 앞서

 

react query의 기능 업데이트가 주기적으로 이루어졌고 최근 v5로 넘어오면서 여러 가지 변경점들이 발생되었습니다.

 

최근 변경점에 대해서도 확인하고 싶으신 분들은 아래 글들을 참고 부탁드립니다.

 

[React] react-query v5 변경점 알아보기 (1) - 공통 기능 변경

[React] react-query v5 변경점 알아보기 (2) - query 공통 기능 변경

[React] react-query v5 변경점 알아보기 (4) - useInfiniteQuery 기능 변경

 

 

 

useInfiniteQuery란?

 

useInfiniteQuery는 파라미터 값만 변경하여 동일한 useQuery를 무한정 호출할 때 사용됩니다.

 

사용될 수 있다고 생각되는 예시 케이스는 대표적으로 Infinite Scroll이 있을 것으로 생각합니다.

 

 

 

useInfiniteQuery의 사용법은 전반적으로 useQuery와 동일합니다.

 

그렇기 때문에 useQuery와 동일하게 2가지 개념을 기본적으로 알고 계시면 됩니다.

 

  • queryKey
  • queryFn

 

 

 

또한 사용 형태도 useQuery와 동일하게 다음과 같습니다.

 

const res = useInfiniteQuery(queryKey, queryFn);

 

 

 

queryKey와 queryFn이 무엇인지에 대해서는 [React] React Query의 useQuery에 대해 알아보기에 설명해놨기 때문에 필요하신 분은 해당 글을 참고해주시길 바랍니다.

 

 

반응형

 

 

pageParam

 

pageParam은 useInfiniteQuery가 현재 어떤 페이지에 있는지를 확인할 수 있는 파라미터 값입니다.

 

또한 pageParam은 다음과 같이 queryFn의 파라미터 값에서 확인할 수 있습니다.

 

const res = useInfiniteQuery(
    ['infinitePerson'], 
    ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
    params: {
        id: pageParam
    }
}));

 

 

 

기본 값은 undefined이기 때문에 값이 없을 경우 초기값으로 5를 넣어주도록 설정해놨습니다.

 

그리고 데이터를 조회해올 때 pageParam값을 api 요청할 때 파라미터 값으로 넣어 사용할 수 있습니다.

 

 

 

 

getNextPageParam과 fetchNextPage

 

getNextPageParam과 fetchNextPage은 공통적으로 다음 페이지에 있는 데이터를 조회해올 때 사용됩니다.

 

그리고 getNextPageParam부터 조금 더 자세하게 설명드리겠습니다.

 

 

 

getNextPageParam은 다음 api를 요청할 때 사용될 pageParam값을 정할 수 있습니다.

 

코드가 작성되는 부분은 다음과 같이 useInfiniteQuery를 정의할 때입니다.

 

const res = useInfiniteQuery(
    ['infinitePerson'], 
    ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
    params: {
        id: pageParam
    }
}), {
    getNextPageParam: (lastPage, allPages) => {
        return lastPage.data.id + 1; // 다음 페이지를 호출할 때 사용 될 pageParam
    },
});

 

 

 

파라미터 값으로 크게 lastPage, allPages 값을 전달받을 수 있습니다.

 

lastPage는 useInfiniteQuery를 이용해 호출된 가장 마지막에 있는 페이지 데이터를 의미합니다.

 

allPages는 useInfiniteQuery를 이용해 호출된 모든 페이지 데이터를 의미합니다.

 

 

 

또한 return 값을 정할 수 있는데 여기서 return 되는 값이 다음 페이지가 호출될 때 pageParam 값으로 사용됩니다.

 

그렇기 때문에 저 같은 경우는 lastPage에 있는 데이터의 id값보다 1 더 큰 값을 return값으로 하여 다음 페이지가 호출될 수 있도록 해놨습니다.

 

 

 

다음으로 fetchNextPage는 다음 페이지의 데이터를 호출할 때 사용됩니다.

 

useInfiniteQuery의 return 값에 포함되며 다음과 같이 버튼을 클릭할 때 실행될 이벤트로 등록해줄 수 있습니다.

 

    const getPersons = () => {
        const res = useInfiniteQuery(
            ['infinitePerson'], 
            ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
            params: {
                id: pageParam
            }
        }), {
            getNextPageParam: (lastPage, allPages) => {
                return lastPage.data.id + 1; // 다음 페이지를 호출할 때 사용 될 pageParam
            },
        });
        
        // 로딩 중일 경우
        if(res.isLoading) {
            return (
                <LoadingText>Loading...</LoadingText>
            )
        }

        // 결과값이 전달되었을 경우
        if(res.data) {
            return (
                <Person.Container>
                    {res.data.pages.map((page) => {
                        const person: Iperson = page.data;

                        return (
                            <Person.Box key={person.id}>
                                <Person.Title>{person.id}.</Person.Title>
                                <Person.Text>{person.name}</Person.Text>
                                <Person.Text>({person.age})</Person.Text>
                            </Person.Box>
                        )
                    })}

                    <Person.NextButton onClick={() => res.fetchNextPage()}>Next</Person.NextButton> {/* 클릭 시 다음 페이지 호출 */}
                </Person.Container>
            )
        }
    }

 

 

 

useInfiniteQuery를 이용해 호출되는 데이터들은 page별로 배열의 요소에 담기게 됩니다.

 

그리고 fetchNextPage를 이용해 호출된 데이터는 배열의 가장 우측에 새롭게 담겨 전달받게 됩니다.

 

현재 코드를 예시로 들면 pageParam의 초기 값으로 5를 넣어놨기 때문에 return 되는 데이터에 [ '5 data' ]와 같이 담겨있습니다.

 

여기서 만약 fetchNextPage를 한번 누를 경우 return 되는 데이터는 [ '5 data', '6 data' ]가 되는 겁니다.

 

한번 더 누르게 되면 return 되는 데이터는 [ '5 data', '6 data', '7 data' ]가 되며 이와 같은 방식으로 점점 쌓이게 됩니다.

 

 

 

 

getPreviousPageParam과 fetchPreviousPage

 

getPreviousPageParam과 fetchPreviousPage는 공통적으로 이전 페이지에 있는 데이터를 조회해올 때 사용됩니다.

 

또한 getNextPageParam과 fetchNextPage와 정확히 정반대의 역할을 수행합니다.

 

위와 동일하게 getPreviousPageParam부터 조금 더 자세하게 설명드리겠습니다.

 

 

 

getPreviousPageParam은 이전 api를 요청할 때 사용될 pageParam값을 정할 수 있습니다.

 

코드가 작성되는 부분은 다음과 같이 useInfiniteQuery를 정의할 때입니다.

 

const res = useInfiniteQuery(
    ['infinitePerson'], 
    ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
    params: {
        id: pageParam
    }
}), {
    getPreviousPageParam: (firstPage, allPages) => {
        return firstPage.data.id - 1; // 이전 페이지를 호출할 때 사용 될 pageParam 
    },
});

 

 

 

파라미터 값으로 크게 firstPage, allPages 값을 전달받을 수 있습니다.

 

firstPage는 useInfiniteQuery를 이용해 호출된 가장 처음에 있는 페이지 데이터를 의미합니다.

 

allPages는 useInfiniteQuery를 이용해 호출된 모든 페이지 데이터를 의미합니다.

 

 

 

또한 여기서도 return 값을 정할 수 있는데 여기서 return 되는 값이 이전 페이지가 호출될 때 pageParam 값으로 사용됩니다.

 

그렇기 때문에 저 같은 경우는 firstPage에 있는 데이터의 id값보다 1 더 작은 값을 return값으로 하여 이전 페이지가 호출될 수 있도록 해놨습니다.

 

 

 

다음으로 fetchPreviousPage는 이전 페이지의 데이터를 호출할 때 사용됩니다.

 

이 또한 useInfiniteQuery의 return 값에 포함되며 다음과 같이 버튼을 클릭할 때 실행될 이벤트로 등록해줄 수 있습니다.

 

    const getPersons = () => {
        const res = useInfiniteQuery(
            ['infinitePerson'], 
            ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
            params: {
                id: pageParam
            }
        }), {
            getPreviousPageParam: (firstPage, allPages) => {
                return firstPage.data.id - 1; // 이전 페이지를 호출할 때 사용 될 pageParam 
            },
        });
        
        // 로딩 중일 경우
        if(res.isLoading) {
            return (
                <LoadingText>Loading...</LoadingText>
            )
        }

        // 결과값이 전달되었을 경우
        if(res.data) {
            return (
                <Person.Container>
                    {res.data.pages.map((page) => {
                        const person: Iperson = page.data;

                        return (
                            <Person.Box key={person.id}>
                                <Person.Title>{person.id}.</Person.Title>
                                <Person.Text>{person.name}</Person.Text>
                                <Person.Text>({person.age})</Person.Text>
                            </Person.Box>
                        )
                    })}

                    <Person.PrevButton onClick={() => res.fetchPreviousPage()}>Prev</Person.PrevButton> {/* 클릭 시 이전 페이지 호출 */}
                </Person.Container>
            )
        }
    }

 

 

 

위에서 말씀드린 대로 useInfiniteQuery를 이용해 호출되는 데이터들은 page별로 배열의 요소에 담기게 됩니다.

 

그리고 fetchPreviousPage를 이용해 호출된 데이터는 배열의 가장 좌측에 새롭게 담겨 전달받게 됩니다.

 

현재 코드를 예시로 들면 pageParam의 초기 값으로 5를 넣어놨기 때문에 return 되는 데이터에 [ '5 data' ]와 같이 담겨있습니다.

 

여기서 만약 fetchPreviousPage를 한번 누를 경우 return 되는 데이터는 [ '4 data', '5 data' ]가 되는 겁니다.

 

한번 더 누르게 되면 return 되는 데이터는 [ '3 data', '4 data', '5 data' ]가 되며 이와 같은 방식으로 점점 쌓이게 됩니다.

 

 

 

 

hasNextPage와 hasPreviousPage

 

위의 코드들만 확인했을 땐 정상적으로 데이터들을 계속 불러올 수 있을 것으로 보이지만 문제점이 하나 있습니다.

 

useInfiniteQuery의 이름값을 하듯이 pageParam의 값은 무한하게 증가하고 또는 감소할 수 있습니다.

 

하지만 호출되는 데이터는 한계가 분명히 존재합니다.

 

그렇기 때문에 호출되는 데이터를 한정시켜줄 필요가 있습니다.

 

그럴 때 사용되는 것이 hasNextPage와 hasPreviousPage입니다.

 

 

 

먼저 hasNextPage와 hasPreviousPage는 useInfiniteQuery의 return값에 포함되며 boolean 타입을 가집니다.

 

결국 true or false 값이 저장되는데 true인지 false인지를 결정해주는 곳은 getNextPageParam과 getPreviousPageParam입니다.

 

이 두 함수의 return 값에 다음 api 호출에 사용될 pageParam값이 정상적으로 담겨있을 경우 hasNextPage와 hasPreviousPage는 true로 저장됩니다.

 

하지만 반대로 값이 담겨 있지 않은 경우는 false로 저장됩니다.

 

즉, 이를 활용하기 위해 getNextPageParam과 getPreviousPageParam에서 다음과 같이 한계 값을 정해주며 pageParam을 return 해줄 수 있습니다.

 

const res = useInfiniteQuery(
    ['infinitePerson'], 
    ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
    params: {
        id: pageParam
    }
}), {
    getNextPageParam: (lastPage, allPages) => {
        return lastPage.data.id < 10 && lastPage.data.id + 1; // 다음 페이지를 호출할 때 사용 될 pageParam
    },
    getPreviousPageParam: (firstPage, allPages) => {
        return firstPage.data.id > 1 && firstPage.data.id - 1; // 이전 페이지를 호출할 때 사용 될 pageParam 
    },
});

 

 

 

이렇게 코드를 수정해도 pageParam에는 undefined나 false 등의 값들이 담겨있기 때문에 fetchNextPage나 fetchPreviousPage 이벤트가 실행되면 에러가 발생함에도 불구하고 api를 호출하게 됩니다.

 

이를 방지하기 위해 hasNextPage와 hasPreviousPage 값을 활용할 수 있습니다.

 

이 두 값이 true가 나올지 false가 나올지는 위에서 정의해줬기 때문에 결과적으로 true가 나올 때만 fetchNextPage와 fetchPreviousPage 이벤트가 실행될 수 있도록 다음과 같이 수정해줄 수 있습니다.

 

const getPersons = () => {
    const res = useInfiniteQuery(
        ['infinitePerson'], 
        ({ pageParam = 5 }) => axios.get('http://localhost:8080/person', {
        params: {
            id: pageParam
        }
    }), {
        getNextPageParam: (lastPage, allPages) => {
            return lastPage.data.id < 10 && lastPage.data.id + 1; // 다음 페이지를 호출할 때 사용 될 pageParam
        },
        getPreviousPageParam: (firstPage, allPages) => {
            return firstPage.data.id > 1 && firstPage.data.id - 1; // 이전 페이지를 호출할 때 사용 될 pageParam 
        },
    });

    // 로딩 중일 경우
    if(res.isLoading) {
        return (
            <LoadingText>Loading...</LoadingText>
        )
    }

    // 결과값이 전달되었을 경우
    if(res.data) {
        return (
            <Person.Container>
                {res.data.pages.map((page) => {
                    const person: Iperson = page.data;

                    return (
                        <Person.Box key={person.id}>
                            <Person.Title>{person.id}.</Person.Title>
                            <Person.Text>{person.name}</Person.Text>
                            <Person.Text>({person.age})</Person.Text>
                        </Person.Box>
                    )
                })}

                <Person.NextButton onClick={() => res.hasNextPage && res.fetchNextPage()}>Next</Person.NextButton> {/* 클릭 시 다음 페이지 호출 */}
                <Person.PrevButton onClick={() => res.hasPreviousPage && res.fetchPreviousPage()}>Prev</Person.PrevButton> {/* 클릭 시 이전 페이지 호출 */}
            </Person.Container>
        )
    }
}

 

 

 

이와 같이 수정을 하게 되면 다음 데이터가 있을 때만 fetchNextPage, fetchPreviousPage가 실행되어 우리가 생각하는 이상적인 결과를 보여줄 수 있습니다.

 

 

 

 

 

 

 

이상으로 React Query의 useInfiniteQuery에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글