프로젝트 이모저모/HoduMarket 프로젝트

오픈마켓 프로젝트 버그) 최상단 훅 호출 불가, useQueries로 해결

Ella Seon 2023. 8. 11. 11:30

0. 버그 현황

- 이틀째 시달리는 에러.....어떻게 해결하면 좋을지 모르겠음

- redux-toolkit 의 createAsyncThunk 에서 react-query 로 장바구니 항목을 불러오는 페이지 리팩토링 하는 중 고난이도의 문제가 생김

- 장바구니 정보를 가져오는 API 를 불러오고 거기서 product_id 값을 반환 받으면 그 product_id 값을 이용해서 상세 상품 정보를 불러오는 API 를 통신해야했다.

장바구니 불러오는 API 에서 product_id 를 빼오기 위해서 결국 반복문을 쓸 수 밖에 없었다. 

// 장바구니 불러오는 API 응답값
{
    "count": Int,
    "next": String,
    "previous": String,
    "results": [
        {
			"my_cart": Int, // 카트 고유번호
			"cart_item_id": Int, // cartItem의 고유번호
            "product_id": Int, // 상품 아이디
            "quantity": Int // 장바구니에 담긴 상품의 개수
        }
    ]
}

- 그래서 리덕스 툴킷을 사용할 때는 반복문 안에 API 호출하는 코드를 넣었음

  // 장바구니 정보 가져오기
  useEffect(() => {
    if (TOKEN) {
      dispatch(fetchGetCartList(TOKEN));
    }
  }, [TOKEN, dispatch]);

  // 상품 상세 정보 가져오기
  useEffect(() => {
    const getProductDetails = async () => {
      for (const cartItem of cartItems) {
        // 이미 상품 상세 정보를 가져온 경우는 제외
        if (cartItem.item) {
          continue;
        }
        dispatch(fetchGetProductDetail(cartItem.product_id));
      }
    };

    if (cartItems.length > 0) {
      getProductDetails();
    }
  }, [cartItems, dispatch]);

  if (cartStatus === "loading") {
    return <Spinner />;
  }

- 하지만, react-query 는 Hook 이기 때문에 반복문안에서 호출할 수 없었음. 왜냐! 훅은 최상단에서 호출해야하기 때문이다. 

const useFetchCartItems = (token: string) => {
  const { data: cartList } = useFetchCartList(token);

  //각 항목의 product_id 로 제품 상세 정보를 가져온다.
  const productDetails = cartList.results.map((item: CartType) => {
    return useFetchProductDetail(item.product_id);
  });

  let cartItems = cartList.results.map((item: CartType, index: number) => {
    return { ...item, productDetail: productDetails[index].data };
  });

  return { cartItems };
};

export default useFetchCartItems;
🔸에러메세지

ERROR

[eslint] src\hooks\queries\useFetchCartItems.tsx Line 10:12: React Hook "useFetchProductDetail" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks Search for the keywords to learn more about each error.

 

1. 해결과정

- 초반에는 react-query가 훅이니까 react-query 없이 하면 문제가 해결된다. 

import { useState, useEffect } from "react";
import productAPI from "../../API/productAPI";
import { CartType } from "../../types/Cart.type";
import useFetchCartList from "./useFetchCartList";

const useFetchCartItems = (token: string) => {
  const { data: cartList } = useFetchCartList(token);
  const [cartItems, setCartItems] = useState<any[]>([]);

  useEffect(() => {
    if (cartList && cartList.results) {
      // map을 통해 각 제품의 상세 정보를 가져오는 Promise의 배열을 만듦.
      const productDetailsPromises = cartList.results.map((item: CartType) => {
        return productAPI.fetchProductDetail(item.product_id); // [Promise] 반환
      });

      // 모든 Promise가 완료되면 결과 배열을 가져와서 cartItems 상태를 설정.
      Promise.all(productDetailsPromises).then((productDetails) => {
        const updatedCartItems = cartList.results.map(
          (item: CartType, index: number) => {
            return { ...item, productDetail: productDetails[index] };
          }
        );
        setCartItems(updatedCartItems);
      });
    }
  }, [cartList]);

  return { cartItems };
};

export default useFetchCartItems;

 

useQueries 로 문제 해결

- 하지만, react-query 를 사용했으니 react-query 로 해결해야되지 않을까!!

useQueries로 해결했다. 여러개의 useQuery 를 동시에 수행해야하는데 렌더링이 거듭되는 사이사이에 계속 쿼리가 수행되어야 한다면 쿼리를 수행하는 로직이 hook 규칙에 어긋날수도 있을때는 useQueries를 사용한다.

 

 

아래와 같이 useQueries 를 사용하면 undefined 가 useQueries의 반환값으로 나온다고 에러가 뜨는데..

 

const useFetchCartItems = (token: string) => {
  // CartList 가져오기
  const { data: cartList } = useFetchCartList(token);
  // console.log(cartList);

  const productIds = cartList.results.map((item: any) => {
    return item.product_id;
  });

  console.log("Product IDs:", productIds);
  const queryInput = productIds.map((productId: string) => {
    return {
      queryKey: ["cartProductDetail", productId],
      queryFn: () => productAPI.fetchProductDetail(productId),
    };
  });
  console.log("Query Input:", queryInput);

  const cartItemsResponses = useQueries(queryInput);

  console.log("윗부분이 오류임");

  // 결과를 기반으로 cartItems 생성
  const cartItems = cartList?.results?.map((item: any, index: any) => ({
    ...item,
    productDetail: cartItemsResponses[index]?.data,
  }));

  return { cartItems };
};

export default useFetchCartItems;

분명 다 잘되는데

  const cartItemsResponses = useQueries(queryInput); 이부분에서 계속 오류가 났다..

뭘까 뭘까 3일째 고민하다가 버전차이인걸 알았다. 하...허탈하다

const cartItemsResponses = useQueries({ queries: queryInput });

 

 

- useQueries v3 에서 v4로 변경되면서 queries 프로퍼티를 가진 객체를 넘겨주는 것이었다 

// v3
const queryResults = useQueries(
  heroIds.map((id) => ({
    queryKey: ["super-hero", id],
    queryFn: () => getSuperHero(id),
  }))
);
/*
  const queryResults = useQueries(
    { 
      queryKey: ['super-hero', 1], 
      queryFn: () => fetchSuperHero(1) 
    },
    { 
      queryKey: ['super-hero', 2], 
      queryFn: () => fetchSuperHero(2) 
    },
    // ...
  );
*/
// v4
const queryResults = useQueries({
  queries: [
    {
      queryKey: ["super-hero", 1],
      queryFn: () => fetchSuperHero(1),
      staleTime: Infinity, // 다음과 같이 option 추가 가능
    },
    {
      queryKey: ["super-hero", 2],
      queryFn: () => fetchSuperHero(2),
      staleTime: 0,
    },
    // ...
  ],
});

3일동안 고민하던거 끝 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ 문서를 잘보자..ㅠㅠ