avatar
Ganymedian

# 4-1 useAxiosFetch 보완

5 months ago
·
5 min read

2024년 7월 13일 오후 10:30

#3 강에서의 useAxiosFetch 와, #4 강 Workthrough 에서의 useAxiosFetch 가 조금 달라진 것을 뒤늦게 확인하였습니다.

#3 강의 useAxiosFetch ver.01 에서는, AbortController 의 axios 버전인 axios.CancelToken.source() 를 사용했습니다만,

제 불찰로, #4 강의 useAxiosFetch ver.02 에서는, AbortController 기능 자체가 누락됐습니다.

useAxiosFetch ver.01 에서의 코드는

const { data, loading, error } = useAxiosFetch();

이렇게 훅 자체에서 fetch action 이 트리거 되는 구성이었는데요..

useAxiosFetch ver.02 의 코드는 이렇게…

const { fetchData, data, loading, error } = useAxiosFetch();

fetch action 펑션을 리턴하는 구조로 바뀌었습니다. useAxiosFetch ver.01 의 코드를 저도 기억하지 못하고 있었고, 당연히 useAxiosFetch ver.02 의 코드가 이어져 왔다고 생각했는데, 혼란을 드리게 되어서 죄송합니다.

어쨌든, useAxiosFetch ver.02 의 방식이 제가 생각했던 그 구조가 맞습니다. 그리고, useAxiosFetch ver.02 의 코드에 AbortController 를 추가해서 완성된 코드를 남기고 넘어가야 겠다는 생각이 들어서 보완 아티클을 작성합니다.

useAxiosFetch ver.03

useAxiosFetch ver.02 에서 AbortController 를 추가하려면, 조금은 더 복잡한 코드가 될 것 같습니다.

useAxiosFetch 훅 내부에서는 fetch 요청을 취소해야 하는지를 판단할 수 없습니다. 취소 요청은 useAxiosFetch 를 호출한 부모 컴포넌트 에서 발생하기 때문이지요.

따라서, useAxiosFetch 가 fetch action 을 취소하는 데에는 두가지 해결방법이 있습니다.

첫번째는, useAxiosFetch ver.03 이 모든 fetch action 에서 직전 fetch action 을 cancel 시키는 것이고,

두번째는, useAxiosFetch ver.03 이 추가로, cancelRequest 펑션을 추가해서 리턴하는 방법입니다.

두가지 방법을 모두 시도해본 결과, cancel 펑션을 추가로 리턴하는 건, 지나친 복잡성을 불러왔고, "YAGNI" (You Aren't Gonna Need It) 원칙에도 맞지 않았습니다.

그래서 첫번째 방법, 모든 fetch action 직전에 이전 fetch action 을 cancel 시키는 방법을 쓰기로 결정하였습니다.

/*  2024-07-14 15:45:35

useAxiosFetch  ver.03
Typescript 로 작성되었지만, JS 에서는 타입 부분만 살짝 제거하고 사용하시면 될 것 같습니다.

*/

import { useState, useRef, useCallback, useEffect } from "react";
import axios, {
  AxiosRequestConfig,
  AxiosResponse,
  AxiosError,
  Canceler,
} from "axios";
import axiosInstance from "@/util/axiosInstance";

interface State<T> {
  data: T | null;
  loading: boolean;
  error: AxiosError | null;
}

function useAxiosFetch<T>() {
  const [state, setState] = useState<State<T>>({
    data: null,
    loading: false,
    error: null,
  });

  const cancelRef = useRef<Canceler | null>(null);

  const cancelPreviousRequest = () => {
    if (cancelRef.current) {
      cancelRef.current("Request canceled due to new request");
      cancelRef.current = null;
    }
  };

  const fetchData = useCallback(
    async (endpoint: string, config: AxiosRequestConfig = {}) => {
      cancelPreviousRequest();

      setState((prev) => ({ ...prev, loading: true }));

      try {
        const response: AxiosResponse<T> = await axiosInstance.request<T>({
          url: endpoint,
          ...config,
          cancelToken: new axios.CancelToken((c) => {
            // 다음 렌더링 또는 fetch 액션에서 참조할 cancel 펑션을 ref 에 보관한다.
            cancelRef.current = c;
          }),
        });

        setState({
          data: response.data,
          loading: false,
          error: null,
        });

        return response.data;
      } catch (error) {
        if (!axios.isCancel(error)) {
          setState({
            data: null,
            loading: false,
            error: error as AxiosError,
          });
        }
        throw error;
      }
    },
    []
  );

  useEffect(() => {
    return () => {
      cancelPreviousRequest();
    };
  }, []);

  return { fetchData, ...state };
}

export default useAxiosFetch;

useAxiosFetch 를 사용하고 있는 부모 컴포넌트에서의 코드는 특별히 바꿔줄 게 없습니다.

이렇게 우리의 useAxiosFetch ver.03 이 완성되었습니다.

조금은 더 복잡해진 느낌이지만, Fetch action에서 AbortController 는 꼭 필요하다고 생각합니다.

fin.







....