blog

TanStack Query와 axios 에러 처리 메모

Front-end / React#TanStack Query#React Query#axios#에러 처리#팁 모음

mutate vs mutateAsync

  • mutate: 반환값이 없다(void). 결과는 onSuccess·onError 콜백으로 처리한다. 에러가 나도 throw하지 않는다.
  • mutateAsync: Promise를 반환한다. 응답을 await로 바로 받아 이어서 처리할 때 쓴다.
const { mutateAsync } = useMutation({ mutationFn: createPost });

const handleSubmit = async () => {
  try {
    const post = await mutateAsync(form);
    router.push(`/posts/${post.id}`);
  } catch {
    // mutateAsync는 실패 시 reject하므로 직접 처리해야 함
  }
};

mutateAsync의 에러를 잡지 않으면 처리되지 않은 Promise rejection이 된다. 대부분의 경우 mutate + 콜백으로 충분하다.

onSuccess 안에서 throw하면

mutationFn이 성공해도 onSuccess 콜백 안에서 에러를 던지면, 그 mutation은 실패로 처리되어 onError가 호출된다. HTTP 200이지만 본문의 code로 실패를 판단하는 API라면, mutationFn 안에서 검사해 던지는 편이 흐름이 명확하다.

mutationFn: async (body) => {
  const { data } = await api.post('/orders', body);
  if (data.code !== 0) throw new Error(data.message);
  return data.data;
},

onError는 언제 호출되나

mutationFn(또는 queryFn)이 반환한 Promise가 reject되면 호출된다.

  • axios는 기본적으로 2xx가 아닌 응답에서 AxiosError를 throw한다.
  • API 함수에서 Promise.reject(...)를 반환하거나 throw해도 마찬가지다.
  • 반대로 HTTP 200에 본문으로 실패를 알리는 API는 직접 throw하지 않는 한 성공으로 처리된다.

axios 에러는 try/catch로 받는다

try {
  await api.get('/me');
} catch (error) {
  if (axios.isAxiosError(error)) {
    console.log(error.response?.status, error.response?.data);
  }
}

axios는 요청이 실패하면 내부에서 AxiosError를 throw하고, 가장 가까운 catch가 받는다. TypeScript에서 catch의 error는 unknown이므로 axios.isAxiosError로 좁혀서 쓴다.

enabled

enabled: false인 쿼리는 자동으로 실행되지 않는다.

  • refetch()를 직접 호출하면 enabled와 관계없이 실행된다.
  • enabled가 false에서 true로 바뀌면, 캐시에 데이터가 없거나 stale인 경우 자동으로 요청한다. 따로 refetch()를 부를 필요가 없다.

사용자 입력이 생길 때까지 요청을 미루는 경우 enabled: !!keyword처럼 조건을 걸어 두면 된다.

const { data } = useQuery({
  queryKey: ['search', keyword],
  queryFn: () => search(keyword),
  enabled: keyword.length > 0,
});