Tanstack Query로 복잡한 API 의존성 관리하기

박애리·2025년 8월 25일
post-thumbnail

캡스톤에서 프로젝트를 하던 중, 해당 프로젝트를 거의 다 만들었을때 그제서야 보이던 오류가 있었고 그걸 해결했던 과정을 정리해보았다.

해당 서비스의 결제 workflow는

모바일 결제 → 리다이렉트 → 리다이렉트 페이지에서 백엔드 API 호출(imp_uid 검증) → 검증 성공 시 DB에 기록 -> 기부내역 생성 (api호출) -> 결제내역 생성(api호출) → 랜덤 보상 제공(api호출)

와 같다.

더럽게 복잡하다.

사실 이 part는 내가 맡은 부분이 아니여서 잘모르는 부분(원래 알아야하는데.. 쓰읍)도 있었는데 이번 기회에 자세히 보고 디테일하게 알고가게 되었다.


여기서 문제

문제는 1번의 기부내역, 결제내역이 생성되면 그에 맞게 랜덤 보상 api도 1번만 호출이 되어야하는데 랜덤 보상 api가 여러번 호출되어 원래 주어져야하는 보상보다 많이 제공되고 DB에 고스란히 저장되었다.

DB를 확인해 볼수 없는 상황이라 거짓말 안하고 console로 50번 넘게 출력해본거 같다.

어떻게 해결을 할까 짱꾸를 굴려봤지만... 부트캠프에서 TanstackQuery배울때 활용할수있는 option들이 많다는 생각만 떠올랐다.

블로그로 찾아보는데 어떻게 검색해야할지도 감이 잡히지않아 결국 gemini의 도움을 빌려 해결을 했다!


우선 원래 code

export const useDonation = (): UseDonationResult => {
    const createDonationMutation = useMutation<
    DonationCreateResponse,
    Error,
    DonationCreateData
({
        mutationFn: (donationData: DonationCreateData) => createDonation(donationData),
            onSuccess: (data: DonationCreateResponse) => {
      if (data?.data?.id) {
        console.log("기부 생성 성공", data);
      } else {
        console.warn("기부 생성 성공했지만 ID가 없습니다.", data);
      }
    },
            onError: (error: Error) => {
              console.error("결제 생성 실패:", error);
            },
    })
    const donationRewardQuery: UseQueryResult<DonationRewardData | null, Error> = useQuery({
        queryKey: ["donationReward"],
        queryFn: fetchDonationReward,
    });
    return {
        createDonationMutation,
        donationRewardQuery,
    }
}

createDonationMutation을 실행하여 기부내역을 생성한 다음 페이지 이동을 하고 donationRewardQuery를 호출하여 랜덤 보상을 받도록 코드를 작성했다.


그리고 해결한 코드는

export const useDonation = (): UseDonationResult => {
  const queryClient = useQueryClient(); 
  const createDonationMutation = useMutation<
    DonationCreateResponse,
    Error,
    DonationCreateData
({
    mutationFn: (donationData: DonationCreateData) =>
      createDonation(donationData),
    onSuccess: (data: DonationCreateResponse) => {
      if (data?.data?.id) {
        console.log("기부 생성 성공", data);
        queryClient.invalidateQueries({ queryKey: ["donationReward"] });
      } else {
        console.warn("기부 생성 성공했지만 ID가 없습니다.", data);
      }
    },
    onError: (error: Error) => {
      console.error("결제 생성 실패:", error);
    },
  });
  const donationRewardQuery: UseQueryResult<DonationRewardData | null, Error> =
    useQuery({
      queryKey: ["donationReward"],
      queryFn: fetchDonationReward,
      staleTime: Infinity,
      gcTime: Infinity,
      refetchOnWindowFocus: false,
      refetchOnMount: false,
      refetchOnReconnect: false,
      enabled: false,)
      // 
    });
  return {
    createDonationMutation,
    donationRewardQuery,
  };
};
// 페이지 이동후 donationRewardQuery를 호출하여 랜덤보상 받는 부분
  const {
    data: apiResponseData,
    isPending,
    isError,
    error,
    refetch, // refetch 함수
  } = donationRewardQuery;
  ...
  useEffect(() => {
    refetch();
  }, [refetch]);

이다.

뽀인트를 설명할게요.

우선 랜덤 보상 api 호출을 할때
staleTime: Infinity와 gcTime:Infinity 때문에 캐시된 데이터는 '영원히 fresh'하고 '영원히 존재'하게 됩니다.

그래서 어떠한 이유에서 리렌더링이 되던지 api가 재호출이 되더라도 캐시된 데이터가 fresh 하기 때문에 fetchDonationReward를 또 실행하지 않게됩니다.

그런데 아예 새로운 기부내역이 생성이 되었다면?

createDonationMutation에서 기부내역이 성공적으로 생성되었을때 랜덤보상 key["donationReward"]를 넣은 queryClient.invalidateQueries을 설정하여 해당 캐시를 stale상태로 만들면 됩니다!

그리고 더욱 확실한 방법으로 랜덤 보상을 받는 페이지로 이동을 했을때 refetch()를 호출하자!

*useEffect의 의존성 배열에 refetch만 있으므로, 이 훅은 컴포넌트가 처음 마운트될 때 단 한 번만 실행됨. refetch 함수 자체가 변경될 일은 없기 때문.


결과적으로, "결제를 요청할 때마다 새로운 보상을 딱 한 번만 가져오고, 그 이후에는 페이지 내에서 불필요한 API 호출은 발생하지 않는다 !" .

  • 사용한 option 간략히 설명하기 
  staleTime: Infinity, // 캐시된 데이터를 계속'fresh' 상태를 유지하도록 설정
  gcTime: Infinity, // 캐시된 데이터를 가비지 컬렉션하지 않고 계속 유지
  refetchOnWindowFocus: false, // 사용자가 해당 브라우저에 포커스를 잃었다가 다시 포커스할때 자동으로 쿼리를 재요청 할지 결정
  refetchOnMount: false, // 컴포넌트가 새롭게 마운트될 때 자동으로 쿼리를 요청할지 결정 
  refetchOnReconnect: false, // 네트워크 재연결 시 자동 리프레시 방지
  enabled: false, // 초기 렌더링 시에는 쿼리를 비활성화 (뒤에서 다시 활성화)

0개의 댓글