@tanstack-query (구 react-query)를 사용하며 만난 이슈 (feat. useInfiniteQuery)

Jeonghun·2023년 12월 4일
2

React

목록 보기
18/21


프로젝트를 진행하면서 여러 페이지를 넘나들며 기능 테스트를 진행하던 중, 동일한 페이지에 재접근 했을 때 개발자 모드의 네트워크 탭에 이전과 같은 API 요청을 통해 동일한 데이터를 리패치 하는 과정이 담기는걸 보고, 이를 효율적으로 관리해서 불필요한 데이터 호출을 줄일 수 있는 방법이 없을까? 에 대해 고민하게 되었고, 요즘 프론트엔드 시장에서 빼놓을 수 없는 '리액트 쿼리' 라이브러리가 생각나 도입하게 되었다.

이 때 해당 라이브러리를 사용하면서 발생했던 이슈와 해결 과정에 대해 포스팅 하고자 한다.

Tanstack-query 란 무엇인가?

우선 포스팅을 시작하기에 앞서 Tasntack-query가 무엇인지 간단히 설명하는게 좋을 것 같다.

기존 React-query 라는 이름으로 운영하던 패키지가 React 뿐만 아니라 다른 프레임워크에 대한 기술을 지원하면서, 패키지 네임이 Tanstack-Query로 변경되었다.

공식문서에서 Tanstack-Query는 Server State 즉, 서버에서 받아오는 데이터의 상태를 관리하기 위한 최고의 라이브러리라고 칭한다. 이를 뒷받침 하듯 요즘 채용공고를 확인해보면 우대사항에 빠지는 회사가 거의 없을 정도로 프론트엔드 개발 시장에서 가장 많이 사용되는 라이브러리 중 하나이다.

React-Query를 이용하면 간단하게 다음과 같은 장점을 가질 수 있다.

  1. 자동 데이터 패칭 및 캐싱 : 리액트 쿼리는 컴포넌트가 마운트되거나 업데이트될 때 지정한 queryFn을 이용하여 자동으로 데이터를 패칭하며, 패칭된 데이터를 캐싱한다.
  1. 쿼리 데이터 자동 초기화 : 1항에서 언급한 캐싱 데이터를 통해 동일한 데이터 요청을 반복하지 않고 효율적으로 관리할 수 있으며, 캐싱된 데이터의 gcTime이 지나거나 특정한 조건을 충족할 경우 자동으로 캐시를 초기화하여 최신 데이터를 유지한다.
  1. 데이터 상태 관리 : 데이터 패칭 중 로딩, 에러 등의 상태를 쉽게 관리할 수 있도록 관련 기능을 제공한다.

자세한 사용 방법에 대해서는 공식문서를 참고하길 바라며, 이번 포스팅에서 Tanstack-Query 에 대한 설명은 이 정도로 마무리 하겠다.

마주한 문제점

위와 같은 장점 때문에 최근 진행한 프로젝트의 API 호출 방식을 Tanstack-query를 적용하여 리팩토링 하던 중, 한 가지 문제를 겪었다.

댓글 목록에 useInfiniteQuery를 이용해 무한스크롤을 적용하였더니, 페이지를 이탈했다가 다시 해당 페이지에 접근했을 때 이전에 불러왔던 댓글 목록을 한 번에 모두 재요청해서 다시 불러오는 것이었다.

staleTime 조정

처음엔 문제가 Tanstack-query의 staleTime으로 인해 발생하는 것일까 라는 생각이 들어 이를 조정해보았다.

여기서 staleTime이란 캐싱된 데이터가 새로고침 되지 않는 상태로 얼마동안 유지할 것인지 설정할 수 있는 시간으로, 해당 시간 동안에는 쿼리를 초기화 하지 않아 API 요청을 방지한다.

staleTime을 지정해주지 않을 경우 기본 값은 '0'으로 설정되기에 쿼리키에 대한 데이터가 캐싱된 직후, 즉시 다시 리패치될 가능성이 있다는 얘기다.

아래와 같이 staleTime을 1분으로 지정해보았으나,

// useCommentQuery.ts

import {
  useQueryClient,
  useInfiniteQuery,
  InfiniteData,
  InfiniteQueryObserverResult,
} from '@tanstack/react-query';
import { getCommentsData } from '@/services/comments';
import { CommentsDataInterface } from '@/types/comment';

export interface CommentQueryResponse extends CommentsDataInterface {
  nextPage: number | null;
}

const COMMENTS_PER_PAGE = 10;
export const QUERY_KEY = 'comments';

const useCommentQuery = (postId: number): UseCommentQueryReturnType => {
  const queryClient = useQueryClient();

  const fetchComments = async (pageParam = 1) => {
    const response = await getCommentsData(postId, pageParam, COMMENTS_PER_PAGE);

    return {
      ...response,
      nextPage: response.hasNext ? pageParam + 1 : null,
    };
  };

  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery<CommentQueryResponse>({
    queryKey: [QUERY_KEY, postId],
    queryFn: ({ pageParam }) => fetchComments(pageParam as number),
    getNextPageParam: (lastPage) => lastPage.nextPage,
    initialPageParam: 1,
    staleTime: 1000 * 60 * 1, // staleTime을 1분으로 설정
  });

  const moreDataHandler = () => {
    if (hasNextPage) {
      return fetchNextPage();
    }
  };

  return { data, moreDataHandler };
};

export default useCommentQuery;

staleTime으로 지정된 1분이 지나자 같은 문제가 발생했다.

그럼 뭐가 문제일까?

Tanstack-Query의 캐싱 매커니즘을 생각해보자.

이 친구는 캐시 관리를 상당히 빠듯하게 하는 라이브러리다. 한 번 지정된 QueryKey로 API를 호출해 데이터를 가져오면, 해당 QueryKey에 대한 staleTime이 경과된 이후에 데이터를 새로 패칭하는게 아니라, 들고있던 staleTime이 경과된 QueryKey에 대한 데이터를 모두 다시 다 패칭한다.

즉, useInfiniteQuery를 이용해서 데이터를 패칭할 경우, 하나의 QueryKey로 페이지가 넘어갈 때 마다 데이터가 하나씩 쌓이게되고 staleTime이 경과하면 쌓여있던 데이터를 모두 다시 패칭하게 되는 것이다.

해결 방안은?

위에서 언급한 문제를 해결할 수 있는 방법에는 뭐가 있을까.

  1. 우선 첫 번째 방법은 staleTime을 아주 크게 설정하는 것이다. 그렇게 하면 staleTime이 끝날 때 까지 페이지를 새로고침 하지 않는 이상 데이터 캐싱이 만료되지 않게 막아줄 수 있다.

    예를 들어, 극단적으로 staleTime에 Infinity를 주게되면 새로고침 이전에는 절대로 새로운 데이터를 패칭하지 않고 기존의 데이터를 그대로 가지고 있게 된다.

  2. 두 번째 방법은 페이지를 떠나거나 다시 접근하는 순간 해당 QueryKey에 대한 데이터와 쿼리를 모두 초기화 해서 재접근시 아무런 데이터를 가지고 있지 않도록 만들어주는 것이다.

    이렇게 할 경우 저장된 캐시 데이터와 쿼리가 없기 때문에 tanstack-query는 데이터를 첫 페이지 부터 다시 패칭하게 된다.

필자의 경우 해당 기능을 적용하는 곳이 커뮤니티 페이지의 게시글과 댓글 기능이었기에, 유저에게 최신의 데이터를 보여주는것이 중요한 부분이었다.

그렇기에 두 번째 방법을 채택하여 문제를 해결해 나가기로 했다.

캐시 삭제를 통한 문제 해결

두 번째 방법을 통해 해결하기 위해 어떤식으로 접근할 수 있을까?

필자는 useEffect 의 클린업 함수를 이용해보기로 했고, 아래와 같이 코드를 추가했다.

// useCommentQuery.ts

useEffect(() => {
    return () => {
      queryClient.removeQueries({ queryKey: [QUERY_KEY, postId] });
    };
  }, [postId, queryClient]);

클린업 함수를 이용하여 컴포넌트가 언마운트 될 때 해당 이벤트를 발생시키는 방법을 통해 유저가 페이지를 떠날 때 removeQueries 로 QueryKey에 대한 데이터를 삭제해주는 방식이다.

그 결과 ..

뒤로가기를 통해 페이지를 이탈 후 다시 접근했을 때 이전과 다르게 모든 데이터를 리패치 하지 않고, 첫 페이지 부터 정상적으로 패칭하는것을 볼 수 있다.

profile
안녕하세요, 프론트엔드 개발자 임정훈입니다.

0개의 댓글