안녕하세요 TanStack query에 InfiniteQuery를 사용하면서 발생한 문제에 대해 정리해보려고합니다.
더좋은 의견과 방법이 있으면 댓글 부탁드립니다 :)
발생한 문제
저희 갤러리에는 무한 스크롤기능과 함께 Filter 기능이 존재합니다. Filter는 체크박스를 이용한 Filter와 SelectBox를 이용한 Filter 2가지가 존재하고 Context API를 통해서 상태관리를 했고 상태변화가 있을때 Query-params로 Filter 처리를 서버요청에 할 수 있도록 했습니다.
그리고 갤러리 Item 마다 좋아요를 누를 수 있는 button이 존재합니다. button의 상태는 useState로 관리하고 페이지가 렌더링 될때 부모 컴포넌트로 부터 초기 데이터를 받고 클릭 이벤트가 발생하면 컴포넌트 내에서 상태변화를 주고, 서버에 요청을 보냅니다. 그리고 새롭게 렌더링 됬을때 부모로 부터 받은 초기 데이터가 새롭게 반영된 좋아요 정보를 받고 좋아요 컴포넌트가 그것을 반영합니다.
여기서 발생한 문제는, TanStack-query는 통상적으로 각 key마다 데이터를 캐싱하고 Key가 변경되지 않는한 데이터를 새로 패칭하지 않습니다. (물론 gcTime으로 설정해놓은 시간이 지나면 다시 캐싱을 진행합니다.)
그렇기에 좋아요를 누르고 필터상태를 바꿨다가 좋아요를 눌렀던 필터 상태로 돌아오게 되면 좋아요 눌렀던 상태가 반영이 되지 않은 문제가 발생합니다.
무한스크롤 로직
다음과 같은 로직 캐싱처리 로직에서 문제 발생하는것을 확인했습니다.
문제 진단하기
cached된 데이터를 데시 패칭하기 위해서는 캐시된 데이터를 초기화 해야됩니다.
TanStack Query에서 캐싱을 초기화하는 두 가지 방법은 refetch와 invalidateQueries입니다.
- refetch: 이 방법은 해당 쿼리를 즉시 다시 실행하여 새로운 데이터를 가져옵니다. 이 방법은 주로 특정 이벤트나 사용자의 트리거에 의해 즉시 데이터를 새로 고치고자 할 때 사용됩니다.
- invalidateQueries: 이 방법은 지정된 쿼리 또는 쿼리 그룹을 무효화하여 해당 데이터를 다시 요청하도록 합니다. 이 방법은 일반적으로 쿼리에 영향을 주는 매개변수나 필터가 변경되었을 때 사용됩니다. 예를 들어, 필터링이 변경되었을 때 이전 캐시된 데이터를 무효화하고 새로운 데이터를 가져와야 할 때 사용됩니다.
Filter에 변경에 따라 QueryKey를 무효화시켜서 데이터 패칭을 하도록 했습니다.
레퍼런스 찾아보기
사용할 방법에 대해 맞는지에 대해 고민을 했습니다. 필터가 변경될때마다 QueryKey를 무효화시키고 새롭게 데이터 패칭을 진행하는게 맞나?? 라는 생각이 들었고, 현재 활발히 사용중인 사이트를 레퍼런스해서 내 의견이 맞는지 분석해봤습니다.
오늘의집
오늘의집에서는 제가 사용중인 무한스크롤과 SelectFilter 그리고 좋아요 기능을 제공하고 있습니다.
네트워크 창에서 내용을 확인해 보면 첫 렌더링때
https://ohou.se/contents/card_collections.json?order=recommend&page=1&per=48&v=4
다음과같은 요청을 통해서 초기 데이터를 불러오고 있음을 확인했습니다.
필터링 할때마다 네트워크 상태보기
또한, 오늘의집과 같은 사이트의 경우 네트워크 창에서 필터링할 때마다 데이터를 다시 불러오는 것을 확인할 수 있습니다.
따라서 필터가 변경될 때마다 데이터를 새로고침하여 새로운 결과를 반영하는 것이 괜찮은 방법이 될 수 도 있겠다 생각했습니다.
적용하기
1. queryKey 관리하기
Filter가 동작할때, 캐시를 무효화 하려면 현재 캐싱된 query에 대한 key값이 필요합니다.
그래서 Recoil을 이용해서 해당 querykey를 보관하고, Filter가 동작하는곳에서 무효화할 수 있도록 로직을 구현했습니다.
components/gallery/infiniteScrollContainer.tsx
...
const location = useLocation();
const queryClient = useQueryClient();
const setQueryKey = useSetRecoilState(galleryQueryKey);
const queryObject = QueryString.parse(location.search, {
ignoreQueryPrefix: true,
});
useEffect(() => {
setQueryKey(JSON.stringify(['infiniteGallery', queryObject as FilterType]));
}, [queryObject, setQueryKey]);
...
Filter.tsx
const GalleryCheckFilter = () => {
const { filter, onChangeFilter, onResetFilter } = useFilterContext();
const queryClient = useQueryClient();
const queryKey = useRecoilValue(galleryQueryKey);
const isDesktop = useMedia('(min-width: 480px)');
// eslint-disable-next-line consistent-return
const onClickHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const { checked, name, value } = e.currentTarget;
...
queryClient.invalidateQueries(JSON.parse(queryKey));
}
정리
안되는 문제에 대해서 정리를 하는 습관을 들이고 싶었다. 일단 기능적인 부분에 대해서 로직을 정리하고 어떤부분이 안되는지 확인,
그리고 안되는부분을 해결하기 위해 어떤걸 확인했어야 했는지 정리하고, 필요한부분에 대해 찾아보고 공부했다.
그리고 수정하려는 부분이 맞게 수정하는지에 대한 레퍼런스 조사를 진행했고, 수정후에 결과가 잘나오는것을 확인했다.
아직은 많이 미숙한 부분이 있지만 차근차근 정리하면서 해결해야지.
'문제 해결하기 - FE' 카테고리의 다른 글
react-hook-form 컴포넌트 의존성 관리하기 (0) | 2024.04.05 |
---|---|
CRA(Craco)에서 Vite로 전환기 - 1 (0) | 2024.03.19 |
husky + lint-staged가 필요한 이유 (prettier + eslint) (1) | 2024.03.06 |
recoil -> Tanstack-query 무한 스크롤 구현 (0) | 2024.03.05 |
Recoil / Tanstack query 역할 분리하기 (0) | 2024.03.05 |