API 호출 2번하는 오류

milkbottle·2024년 6월 15일
0

React

목록 보기
24/33

API 트러블슈팅

현상

const useFetch = (endpoint, options = {}) => {
  const { isMocked = false, method = "get", params = {}, body = {} } = options;
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchData = async () => {
    console.log(
      `endpoint: ${endpoint} method: ${method} params: ${JSON.stringify(params)} body: ${JSON.stringify(body)}`,
    );
    try {
      setLoading(true);
      const service = isMocked ? mockService : apiService;
      let response;
      switch (method) {
        case "GET":
          response = await service.get(endpoint, params);
          break;
        case "POST":
          response = await service.post(endpoint, body);
          break;
        case "PUT":
          response = await service.put(endpoint, body);
          break;
        case "DELETE":
          response = await service.delete(endpoint, params);
          break;
        default:
          response = await service.get(endpoint, params);
      }
      setData(response);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [endpoint, method, JSON.stringify(params), JSON.stringify(body)]);

  return { data, loading, error, fetchData };
};

다음의 커스텀 훅을 만들고 사이트를 테스트를 했다.

그런데 /lectures/categories부분에서 2번 호출되는 경우가 있었다.

분석

먼저 lectures/categories를 호출하는 컴포넌트가 어디인지부터 확인해야했다.

const Home = () => {
  const [selectedCategory, setSelectedCategory] = useState(0);
  const {
    data: topLanguages,
    loading: topLanguagesLoading,
    error: topLanguagesError,
  } = useFetch(ENDPOINTS.TOP_LANGUAGES, { isMocked: true, method: "GET" });
  const {
    data: lectures,
    loading: lecturesLoading,
    error: lecturesError,
    fetchData: fetchLectures,
  } = useFetch(ENDPOINTS.LECTURES, {
    isMocked: false,
    method: "get",
    params: { category: categoryKeywords[selectedCategory].eng_category },
  });
  useEffect(() => {
    fetchLectures();
  }, [selectedCategory]);
  if (topLanguagesLoading || lecturesLoading) return <Loading />;
  if (topLanguagesError || lecturesError) return <Error />;
  return <컴포넌트 />;
};

selectedCategory를 통해 카테고리를 선택하고, 그 값이 바뀌면 fetchLectures를 한다.

useFetch(ENDPOINTS.LECTURES)를 해야하는 경우는 다음과 같다.

  • 최초 Home 컴포넌트에 들어갈 때
  • selectedCategory가 변경될 때

위의 코드는 useFetch내부의 useEffect를 통해 최초에 호출된다.


최초 Home 컴포넌트에 진입 시, 1번째 useFetch가 작동한다.

그리고, selectedCategoryHome 컴포넌트에 진입 시 useEffect에 의해 fetchLectures를 한 번 더 호출하는 것이다.

Home 컴포넌트에 들어간 후 selectedCategory를 바꾸면, 이때는 제대로 fetchLectures를 한 번만 호출한다.

  1. 즉, 최초 Home 컴포넌트에 진입 시 fetch를 2회 호출한다.

  2. Home 컴포넌트에 진입한 상태에서 selectedCategory를 바꿀 땐 정상적으로 fetch를 1회 호출한다.

1번의 경우를 수정하면 되는 것이다.

해결법

useEffect에는 의존성배열을 제공하기에 이 값이 변경될때마다 호출하도록 할 수 있다.

즉, useFetch 커스텀 훅에 의존성배열을 입맛대로 조정할 수 있는 기능을 추가하면 된다.

const useFetch = (endpoint, options = {}) => {
  const {
    isMocked = false,
    method = "get",
    params = {},
    body = {},
    dependencies = {},
  } = options;
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData();
  }, [
    isMocked,
    endpoint,
    method,
    JSON.stringify(params),
    JSON.stringify(body),
    JSON.stringify(dependencies),
  ]);

  const fetchData = async () => {
    console.log(
      `endpoint: ${endpoint} method: ${method} params: ${JSON.stringify(params)} body: ${JSON.stringify(body)} dependencies: ${JSON.stringify(dependencies)}`,
    );
    try {
      setLoading(true);
      const service = isMocked ? mockService : apiService;
      let response;
      switch (method) {
        case "GET":
          response = await service.get(endpoint, params);
          break;
        case "POST":
          response = await service.post(endpoint, body);
          break;
        case "PUT":
          response = await service.put(endpoint, body);
          break;
        case "DELETE":
          response = await service.delete(endpoint, params);
          break;
        default:
          response = await service.get(endpoint, params);
      }
      setData(response);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  return { data, loading, error, fetchData };
};

dependencies라는 값을 추가한다.

const Home = () => {
  const [selectedCategory, setSelectedCategory] = useState(0);
  const {
    data: topLanguages,
    loading: topLanguagesLoading,
    error: topLanguagesError,
  } = useFetch(ENDPOINTS.TOP_LANGUAGES, { isMocked: true, method: "GET" });
  const {
    data: lectures,
    loading: lecturesLoading,
    error: lecturesError,
  } = useFetch(ENDPOINTS.LECTURES, {
    isMocked: false,
    method: "get",
    params: { category: categoryKeywords[selectedCategory].eng_category },
    dependencies: { selectedCategory: selectedCategory },
  });
  if (topLanguagesLoading || lecturesLoading) return <Loading />;
  if (topLanguagesError || lecturesError) alert("에러가 발생했습니다.");
  return <컴포넌트 />;

그리고 Home 컴포넌트에서는 useFetch를 사용할때 dependencies를 넘긴다.

이렇게 되면, dependencies가 변경되면 1회면 호출을 하게 된다.

최종적으로 최초로 페이지에 접근해도 fetch를 1번만 하게 된다.

0개의 댓글