프로젝트

[react-query] useQuery의 조건문 쿼리

Yuuuki 2024. 4. 23. 01:17

브랜드 카테고리별로 브랜드별 상품을 브랜드페이지에서 보여주려고 하는데, brandId에 따라 조건부 쿼리를 통해 fetch가 달라진다.

 

brandId

⭕️ : 브랜드별 상품 fetch (useQueryGetBrand)

❌ : 전체상품 fetch (useQueryGetProducts)

 

 

조건문과 useEffect

❗️React 훅은 컴포넌트의 최상위 수준에서만 호출되어야 하며, 조건문이나 반복문 내부에서 호출할 수가 없다.

 

훅의 동작이 예측할 수가 없어지기 때문이다. React는 컴포넌트의 상태 변화나 props에 따라 리렌더링 되기때문에, 조건문이나 반복문 내부에서 호출하게되면, 해당 조건이나 반복에 의해 리렌더링으로 쿼리 인스턴스가 생성되고 이전 쿼리가 새로운 쿼리로 대체될수 있고, 예기치 않은 결과를 초래할 수 있다.

 

⭐️ 즉, react는 훅이 호출되는 순서가 항상 동일해야 정상적으로 작동되는데, 훅의 순서를 바꿀만한 코드를 중간에 삽입하면 예상치 못한 오류가 발생됩니다. 그렇기 때문에 조건문/반복문 내 훅 사용을 지양해야한다!

 

function BrandsPage(props: { searchParams: { brandId?: string } }) {
  const brandId = props.searchParams.brandId;

  let isFetchTotalProducts = !brandId;
  const [products, setProducts] = useState<Product[]>([]);
  
  const { data: brands } = useQueryGetBrands();
  const { data: brandResult } = useQueryGetBrand(Number(brandId));
  const { data: productsResult } = useQueryGetProducts(isFetchTotalProducts);

  useEffect(() => {
    if (brandId && brandResult) {
      setProducts(brandResult.products);
    } else {
      setProducts(productsResult || []);
    }
  }, [brandId, brandResult, productsResult]);

}

 

👉🏻 이럴 경우에, useEffect을 사용해 문제를 해결할 수 있는데, useEffect는 sideEffect를 수행할수 있도록 설정하는 훅으로, 해당 조건이 만족했을때 원하는 효과를 발생시킬 수 있다. 그래서, dependency (brandId, brandResult, productsResult)가 변경될때마다 products 상태를 업데이트 하는 효과를 발생시키고 조건에 따라 상태를 업데이트 한다.

 

useQuery의 enabled

 

❗️각 useQuery문에 존재하는 api call을 필요한 경우에만 하기 위해, enabled 조건을 걸어두었다.

import api from "@/apis";
import { useQuery } from "@tanstack/react-query";

export default function useQueryGetBrand(brandId: number) {

  return useQuery({
    queryKey: ["brands", { brandId }],
    queryFn: () => api.brands.getBrand(brandId),
    enabled: !!brandId,
  });
}

 

enabled를 brandId가 존재할때만, api를 호출하도록 설정한다.

 

import api from "@/apis";
import { useQuery } from "@tanstack/react-query";

export default function useQueryGetProducts(enabled: boolean = true) {

  return useQuery({
    queryKey: ["products"],
    queryFn: api.products.getProducts,
    enabled,
  });
}

 

mainPage에도 사용할것이기 때문에, 기본 enabeld 조건은 true로 설정해두고, 

BrandPage 컴포넌트 함수에서 brandId 유무에 따라 isFetchTotalProducts라는 boolean값을 만들어 이 값으로 enabled를 제어하였다.

 

 

SSR 구현

import api from "@/apis";
import Page from "@/components/Page";
import ProductCardsList from "@/components/ProductCardsList";
import { Product } from "@/types/Product.type";
import BrandLink from "./_components/BrandLink";

async function BrandsPage(props: { searchParams: { brandId?: string } }) {
  const brandId = props.searchParams.brandId;
  let products: Product[] = [];

  if (brandId) {
    let brandResult = await api.brands.getBrand(Number(brandId));
    products = brandResult.products;
  } else {
    products = await api.products.getProducts();
  }

  const brands = await api.brands.getBrands();
  brands.sort((a, b) => a.nameEn.localeCompare(b.nameEn));

  return (
    <Page>
      <div>
        <nav className="mb-16">
          <ul className="grid grid-cols-2 sm:grid-cols-4  gap-x-4 gap-y-2 px-12 ">
            <li className="col-span-2 sm:col-span-4 text-center mb-4 text-lg font-bold ">
              <BrandLink href="/brands" label="ALL" isActive={!brandId} />
            </li>
            {brands.map((brand) => (
              <li key={brand.id} className="list-none ">
                <BrandLink
                  href={`/brands?brandId=${brand.id}`}
                  label={brand.nameEn}
                  isActive={brandId === String(brand.id)}
                />
              </li>
            ))}
          </ul>
        </nav>
      </div>
      {brandId && (
        <h2 className="font-bold text-2xl text-center py-4">
          {products[0].brand.nameEn}
        </h2>
      )}
      <ProductCardsList products={products} />
    </Page>
  );
}

export default BrandsPage;

 

SSR로 구현했을시엔, 조건문으로 axios 호출을 한다.