본문 바로가기
sns 프로젝트

React-query Infinite queries 도입하기

by 우주속공간 2023. 6. 6.

✔️ Infinite Queries

 

무한 쿼리 기능은 무한 스크롤이나 더보기와 같이 특정 조건에서 데이터를 추가적으로 받아오는 기능을 구현할때 사용하면 유용한 기능이다.

 

⇒ 무한 쿼리를 사용하기 위해서는 react-query 중에서 useInfiniteQuery 사용하면 된다.

 

 

✔️ infinite queries를 사용한 이유

기존에 구현한 무한스크롤링 방식은 데이터를 가져오는 시점인 target에 도달했을때 새로운 데이터를 가져와서 getData라는 배열에 삽입해서 새로운 데이터를 화면에 로딩하는 방식이었다. 

 

 

 setAddData([...addData, ...res.data.data]);

 

하지만 이러한 방식은 새로운 데이터를 가져와서 로딩하는데에는 문제가 없었지만 하트 기능이나 북마크 기능을 눌렀을때 기존의 데이터를 새롭게 업데이트 하기 힘들었다. 

 

1️⃣ 데이터를 받아오는 기준이 되는 페이지 숫자, 트윗 데이터가 담겨져있는 state(getData)가 연관되어 있지 않고 따로 관리한다는 점

2️⃣ 새로운 데이터를 받아왔을때 무조건 배열에 더해주는 방식

➡️ 이 두가지 방식이 뭔가 코드를 복잡하게 만들고 비효율적으로 작동한다는 느낌을 받았다.

- getData 배열에 특정 페이지에 속한 특정 데이터에 접근하기에도 힘들고(구별하기위한 키 없음, 무작정 더하기만 했음)

- 기존 데이터 업데이트를 위해 react-query 쿼리 무효화를 시켜 새로운 데이터를 가져와도 배열에 해당 페이지 데이터를 업데이트되는게 아니라 그대로 배열에 쌓여서 같은 데이터가 중복되는 문제가 발생했다. 

 

그래서 각 페이지 숫자를 기준으로 데이터에 접근할 수 있는 방법이 필요했고,

pageParams를 기준으로 데이터를 새로 받아오거나 기존의 데이터를 업데이트해서 변화를 줄 수 있는 infinine quries 기능을 사용하기로 했다.

 

✔️useInfiniteQuery 

react-query에서는 무한 쿼리를 지원하기 위해 useInfiniteQuery를 지원한다.

 

 

useInfiniteQuery 반환값

: isFetchingNextPage, isFetchingPreviousPage, fetchNextPage, fetchPreviousPage, hasNextPage 등 

  • fetchNextPage: 다음 페이지를 fetch 할 수 있다.
  • fetchPreviousPage: 이전 페이지를 fetch 할 수 있다.
  • isFetchingNextPage: fetchNextPage 메서드가 다음 페이지를 가져오는 동안 true이다.
  • isFetchingPreviousPage: fetchPreviousPage 메서드가 이전 페이지를 가져오는 동안 true이다.
  • hasNextPage: 가져올 수 있는 다음 페이지가 있을 경우 true이다.
  • hasPreviousPage: 가져올 수 있는 이전 페이지가 있을 경우 true이다.

pageParam이라는 프로퍼티가 존재하며, queryFn에 할당해줘야 한다. 이때 기본값으로 초기 페이지 값을 설정 해줘야한다(1로 할당). getNextPageParam을 이용해서 페이지를 증가시킬 수 있다.

  • getNextPageParam의 첫 번째 인자 lastPage는 fetch 해온 가장 최근에 가져온 페이지 목록이다.
  • 두 번째 인자 allPages는 현재까지 가져온 모든 페이지 데이터이다.
  • getPreviousPageParam도 존재하며, getNextPageParam와 반대의 속성을 갖고 있다.

PageParam

queryFn에 넘겨주는 pageParam가 단순히 다음 page의 값만을 관리할 수 있는 것은 아니다.

pageParam 값은 getNextPageParam에서 원하는 형태로 변경시켜줄 수 있다(객체로 다른 값을 넘겨주거나 pageParams를 원하는 형태로 변경 가능).

 

 

✔️ 코드

//Tweets 수정 전

const getTWeets: any = useQuery(["select", page], tweetSelectApi, {
    refetchOnWindowFocus: false,
    onSuccess: (res: any) => {
      console.log(res);
      setProfile(res.data.profile);
      setTotal(res.data.count);
      if (pageCount > 1) {
        setAddData([...addData, ...res.data.data]);
      } else {
        setAddData([...res.data.data]);
      }
    },
  });

//Tweets 수정 후 

  const { data, hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } =
    useInfiniteQuery(
      ["select"],
      async ({ pageParam = 1 }) =>
        customAxios
          .get("/getTweets/select", {
            params: { pageParam },
          })
          .then((res: any) => {
            setProfile(res.data.profile);
            setTotal(res.data.count);
            return res;
          }),

      {
        getNextPageParam: (lastPage, allPages) => {
      return lastPage.config.params.pageParam + 1;
        },
        refetchOnWindowFocus: false,
      }
    );

  const [addData, setAddData] = useState<Data[]>([]);

  useEffect(() => {
    const observer = new IntersectionObserver(
      async (entries) => {
        console.log(entries);

        if (entries[0].isIntersecting) {
          console.log("is InterSecting");

          // setAddData([...addData, ...getTweets.data.data.data]);

          if (Math.ceil(total / 10) > page.current) {
            fetchNextPage();
            setPageCount((page.current += 1));
            // queryClient.invalidateQueries(["select"]);
          }
       
        }
      },
      {
        threshold: 1,
      }
    );
    if (target) observer.observe(target.current);

    return () => {
      observer.disconnect();
    };
  }, [target, total]);

// TweetBox 코드 수정

  useEffect(() => {
    if (prop.data && prop.data.length > 0) {
      const flattenedData = prop.data.flat();
      setGetData(flattenedData);
      console.log(flattenedData);
    }
  }, [prop.data]);

 

 

✔️ 발생한 오류와 해결 방법

 

1️⃣ 데이터 할당 문제 (flat함수)

useQuery를 이용하여 데이터를 가져오고 있었다면 위와 같은 형태로 작성되어 있었을 것이다.

=> 그래서 useInfiniteQuery로 변경됨으로서 결과로 받는 데이터의 형식이 달라졌기 때문에 그에 맞는 형식으로 바꿔주어야 한다.

 

 

Array.flat() 함수

 중첩 배열에서 평탄화해주고 기본값은 1입니다.

const arr = [['A', 'B'] , 'C', ['D']]

const flatArr = arr.flat();

console.log(flatArr);
// ['A', 'B', 'C', 'D']

 

일단 먼저 API통신으로 가져온 데이터 중에서 tweet data들로만 골라내고 

 <TweetBox
                  data={data?.pages.map((page) => page.data.data)}
                  profile={profile}
                />

 

[[],[],[]]형식의 데이터들을 flat()함수를 통해서 []형식으로 만들어준다. 

   if (prop.data && prop.data.length > 0) {
      const flattenedData = prop.data.flat();
      setGetData(flattenedData);
    }

 

2️⃣  쿼리 무효화시 같은 데이터를 중복으로 가져온 문제(page 변수 설정 문제) 

useState로 관리하는 pageCount를 page 변수로 사용하니까 쿼리 무효화 후에도 예전 페이지 수에 머무름.

그래서 pageCount에서 react-query에서 관리하는 pageParams로 바꿈.(쿼리 무효화를 하면 자동적으로 1로 변경되고 처음부터 현재 페이지까지 새로 받아와짐)

 

 

 

✔️ 참고 및 출처

GitHub - ssi02014/react-query-tutorial: 😃 TanStack Query(aka. react query) 에서 자주 사용되는 개념 정리

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

[react-query] useInfiniteQuery

FlatList와 React Query로 Infinite Scroll 구현하기