상세 컨텐츠

본문 제목

react-query 캐싱 이슈 해결하기! 변경, 삭제할 때 쿼리 업데이트가 안된다?

개발

by 호박너구리의 블로그 2022. 1. 31. 18:00

본문

 

react query는 api 요청과 상태 관리 라이브러리로 최근 많이 선택받고 있습니다. graphql 진영에 apollo client가 있다면, rest 진영에 react-query 가 있는 느낌일까요? 

 

여하튼 기본적으로 api 요청에 많이 사용되던 axios나 fetch와 달리 react-query는 강력한 캐싱 기능을 제공하고 있습니다. 이런 캐싱 기능 덕분에 클라이언트는 매번 동일한 api를 호출하지 않고도 빠른 응답을 받을 수 있게 되었죠.

 

이슈: 수정한 데이터가 반영이 안되었다?

그러나 axios나 fetch만 사용하셨던 분들은 react-query의 캐싱 기능에 대해서 당황해하실 수도 있습니다. 왜냐하면 캐싱 기능으로 인해 데이터가 업데이트되지 않는 상황이 생기기 때문인데요. 구체적으로 묘사해보자면 다음과 같습니다.

 

1. 프로필 페이지에서는 profile을 호출하고 있습니다.

2. 이름을 수정하기 위해 프로필 편집 페이지로 이동하여, 프로필 이름을 수정했습니다.

3. 프로필 페이지로 돌아오니 여전히 profile은 예전 이름을 내려주고 있습니다.

 

 

원인: 설정한 key 값이 변경되지 않으면 업데이트 하지 않는다. 

이는 react-query의 캐싱 방식 때문인데요. react query의 useQuery는 설정한 key 값이 변경되지 않는 이상, 갖고있는 캐싱 데이터를 내려줍니다. 

const profileId = '12345'

const { data } = useQuery(
  ["profile"],
  () => axios({ 
    method: 'get',
    url: `https://url/api/profiles/${profileId}`
  })
)

 

위와 같은 useQuery가 있다고 가정했을 때, "profile"이 변경되지 않으면 계속 같은 값을 내려줄 것입니다.

그런데 프로필 호출 api의 경우, profileId에 따라 당연히 다른 값을 내려줘야 하기 때문에 key 값을 바꾸는 것이 좋겠습니다.

const profileId = '12345'

const { data } = useQuery(
  ["profile", profileId],
  () => axios({ 
    method: 'get',
    url: `https://url/api/profiles/${profileId}`
  })
)

 

이제 profileId가 바뀌면 key값이 달라지기 때문에 새로운 데이터를 받아올 것입니다. 그러나 이슈에서 묘사한 것처럼, 동일한 프로필 id를 가진 상황에서는 여전히 동일한 이슈가 발생합니다.

 

 

해결: 쿼리를 invalidate 한다.

이런 문제를 해결하기 위해서는 기존의 쿼리를 invalidate 해야 합니다. 처음 react-query를 사용하기 위해 최상의 QueryClientProvider에 설정해두었던 queryClient를 사용하면 되는데요. 다른 react 파일에서 사용하고 싶다면 useQueryClient를 호출하면 됩니다.

// queryClient 사용하기
const queryClient = useQueryClient()

// 프로필 이름 변경하는 mutation
const {
  mutate,
} = useMutation(
  (profileName) => axios({
    method: 'post',
    url: `https://url/profiles/${profileId}`,
    data: {
      name: profileName
    }
  }), {
    onSettled: () => queryClient.invalidateQueries(['profile', profileId])
  }
)

 

위와 같이 (프로필 이름을 변경하는) 뮤테이션에서 호출이 끝날 때, 원하는 쿼리를 invalidate 시키는 것입니다. 이 때 함수의 인자로는 타겟으로 하는 쿼리의 key 값이 들어가야 합니다.

 

이제는 다시 프로필 페이지로 돌아왔을 때, 새로운 데이터를 제대로 응답받는 것을 확인하실 수 있을 것입니다!

 

 

별첨1: 새로운 데이터를 받기 전에 값을 넣어두자.

특정 데이터를 편집한 후 쿼리를 invalidate를 할 때, 새로운 데이터를 받아오기 위한 시간이 필요합니다. (웬만하면 오래 걸리지 않지만) 데이터를 받아오기 전에 미리 값을 업데이트 하여, 화면의 깜빡임이나 대기시간을 최소화할 수도 있는데요. queryClient의 setQueryData 함수를 이용하면 가능합니다.

// queryClient 사용하기
const queryClient = useQueryClient()

// 프로필 이름 변경하는 mutation
const {
  mutate,
} = useMutation(
  (profileName) => axios({
    method: 'post',
    url: `https://url/profiles/${profileId}`,
    data: {
      name: profileName
    }
  }), {
    onSettled: () => queryClient.invalidateQueries(['profile', profileId]),
    onSuccess: (newProfile) => {
      queryClient.setQueryData(['profile', profileId], (oldProfile) => {
        return { ...oldProfile, ...newProfile }
      })
    }
  }
)

 

이렇게 하면 setQueryData 함수의 인자로 입력한 key에 해당하는 데이터는 새롭게 return 한 데이터로 변경됩니다!

 

 

별첨2: 쿼리별로 깔끔하게 관리하자.

내 프로필을 받아오는 쿼리를 여러 곳에서 사용한다고 가정해봅시다. 그런 경우, 매번 다른 곳에서 invalidate 함수를 호출하다보면 key 값이 잘못될 수도 있고, 관리하기도 어려워지겠죠. 그래서 저는 자주 사용되는 쿼리/뮤테이션의 경우, 커스텀 훅으로 만들어두는 것을 추천합니다. 

import { useQuery, useQueryClient } from "react-query"
import axios from 'axios'

const useProfile = (profileId) => {
  const queryClient = useQueryClient()

  const { 
    data: profileResponse,
    isLoading
  } = useQuery<{data: Profile}>(
    ['getProfileById', profileId], 
    () => axios({ method: 'get', url: `https://url/profiles/${profileId}`})
  )

  const invalidate = () => {
    queryClient.invalidateQueries(['getProfileById', profileId])
  }

  const setProfileQuery = (targetProfile) => {
    queryClient.setQueryData(['getProfileById', profileId], () => {
      return targetProfile
    })
  }

  return {
    profile: profileResponse?.data,
    isLoading,
    invalidate,
    setProfileQuery
  }
}

export default useProfile

 

이렇게 custom hook으로 만들어두면 반복되는 코드를 줄이고, 쉽게 값이나 함수를 호출하여 사용할 수 있게 됩니다.

 

 

오늘은 react-query의 캐싱과 invalidate에 대해 간단히 살펴보았는데요. 이제 react-query의 캐싱 상황에 조금 더 유연하게 대처할 수 있겠죠? 그럼 오늘도 모두들 해피코딩하세요 :)

 

 

참고 자료

- https://www.zigae.com/react-query-key/

728x90

관련글 더보기

댓글 영역