Tanstack-Query 서버데이터 로컬스토리지에 캐싱하기 💽

9rganizedChaos·2024년 4월 15일
1

웹서비스에서 회원가입 혹은 계정정보 변경 등을 구현할 때,
국가코드 리스트, 혹은 은행코드 리스트 등을 필요로 하는 경우가 많다!
그런데 이 데이터들은 사실상 거의 변하지 않아서, 굳이 매번 서버에 요청을 보낼 필요가 없다.

현재 NextJS를 기반으로 zustand와 tanstack-query를 활용해 개발중이다!
내가 개발하고 있는 서비스에서도 회원들의 계좌 정보를 받아야 하고, 당연히 은행 리스트가 필요하다.
(아래와 같은... 대강 인터넷에서 퍼왔읍니다.)

함께 개발하는 백엔드 엔지니어가 먼저, "은행 리스트의 경우에는 한 번만 요청해서 클라이언트에서 저장해두고 사용해줄 수 있어요?"라고 요청했고, 본격적인 고민이 시작되었다.

첫 번째 시도!

처음에는 Provider에서 API 요청을 보내고, 돌아온 응답을 zustand store에 저장해서 전역에서 활용할 수 있게끔 했다.
대강 아래와 같은 방식!

const StaticDataProvider = ({ children }: PropsWithChildren) => {
  const messageApi = useMessage();
  const { banks, setBanks } = useStaticDataStore(({ banks, setBanks }) => ({
    banks,
    setBanks,
  }));

  const getBankOptions = async () => {
    try {
      const { data: banksData } = await BankRestService.getAllBank();
      setBanks(banksData);
    } catch (error) {
      messageApi?.open({
        type: 'error',
        content:
          '은행 리스트를 불러오는 과정에서 에러가 발생했어요. 잠시 후 다시 접속해주세요.',
      });
    }
  };

  useEffect(() => {
    if (!banks) {
      getBankOptions();
    }
  }, []);

  return children;
};

export default StaticDataProvider;

그런데 위와 같이 작성해놓고 나니, 고민이 들었다.
Tanstack-qeury를 사용하고 있는데, 굳이 서버 데이터를 zustand store에 저장해야 할까? 다른 방법이 있지 않을까?

StaleTime & CacheTime, Infinity로 설정!

그러던 중 발견했던 포스트가 아래 포스트이다!

딜리버블은 항상 동일한 상태를 유지하는 정적 데이터를 꽤 많이 가지고 있습니다. 사용자에게 학습 방법을 알려주는 가이드 데이터, 추천 뉴스 목록 등등. 이러한 데이터들은 실시간성이 중요하지 않아서 수시로 refetch를 해줄 필요가 없습니다. 따라서 해당 데이터들을 불러오는 useQuery의 옵션으로 staleTime을 Infinity로 지정하였습니다. 그리고 cacheTime 또한 Infinity로 지정하였습니다.

그런데 문제는 useQuery는 기본적으로 데이터를 브라우저 메모리에 저장을 한다는 점이었다...!
그말인 즉, 새로 고침하면, 모두 날아간다는 것!

두 번째 시도!

오전 내내 삽질을 하고 나니... "이 코드... 그냥 짜치는대로 둘까?" 하는 유혹이 너무 너무 많이 들었다.

그러다가 마지막으로 전회사 동료에게 도움을 요청드렸다! :)
결론부터 적자면, 요청드리길 너무 잘했다... (👼천👼사👼동👼료👼)

아래 두 레퍼런스 문서를 전달해주셨다!!
https://tanstack.com/query/v5/docs/framework/react/plugins/persistQueryClient/#persistQueryClient
https://github.com/TanStack/query/discussions/3568

그리하여 Provider와 zustand는 클라이언트 상태만을 관리하도록 깰끔하게 보존해두고,
은행 데이터는 Tanstack-Qeury가 알잘딱깔센으로 영구적으로 보관하게 만들 수 있었다!!

일단 추가로 모듈을 설치해주어야 했다.

@tanstack/react-query-persist-client, @tanstack/query-async-storage-persister

그리고 ReactQueryProviders를 아래와 같이 수정!!

export default function ReactQueryProviders({ children }: React.PropsWithChildren) {
  // 중략

  const asyncStoragePersister = createAsyncStoragePersister({
    storage: window.localStorage,
  });
  
  // 중략

  return (
    <PersistQueryClientProvider
      client={queryClient}
      persistOptions={{
        persister: asyncStoragePersister,
        dehydrateOptions: {
          shouldDehydrateQuery: (query) => {
            return query.options.meta?.persist === true;
          },
        },
      }}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </PersistQueryClientProvider>
  );
}

그리고 따로 useBankListQuery 훅을 만들어주었다!

export const useBankListQuery = () => {
  return useQuery({
    queryKey: queryKey('bank').lists(),
    queryFn: async () => BankRestService.getAllBank(),
    staleTime: Infinity,
    gcTime: Infinity,
    refetchOnMount: false,
    meta: { persist: true },
  });
};

여기서 포인트는 "어떻게 원하는 쿼리만 persiser로 스토리지에 저장할 것인가"하는 지점이었는데!

        dehydrateOptions: {
          shouldDehydrateQuery: (query) => {
            return query.options.meta?.persist === true;
          },
        },

위 옵션이 그 포인트이다! 위와 같이 코드를 작성하면,
useQuery를 사용할때, 옵션 값에 meta: {persist: true}를 작성해준 쿼리만 스토리지에 깔끔하게 저장된다!!

원래는 persister를 첨에 세팅하면 아래와 같이 아주 글로벌한 동네방네 데이터들을 전부 스토리지에 저장한다. 이러면 서버 부하 줄이는게 무슨 소용인가...

하여 위와 같이 dehydrateOptions를 세팅해주면!!
bankList만 아름답게 로컬스토리지에 캐싱된다...ㅠ

아름다운 세상...오늘의 개발일기 끝...

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글