skip to Main Content

I have a problem with using my custom hook after using a useQuery hook.

Catalogue.jsx

import { Box, Spinner } from "@chakra-ui/react";
import ProductPage from "../../components/Products/ProductPage";
import useBeers from "../../hooks/useBeers";
import useProductData from "../../hooks/useProductData";

const Catalogue = () => {
  const { data: products, isLoading } = useBeers();

  const productData = useProductData(products);

  if (isLoading) return <Spinner />;

  return (
    <Box>
      <ProductPage productData={productData} />
    </Box>
  );
};

export default Catalogue;

So, i want my useProductData execute only if have already got products from useBeers.
useBeers.js

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

const useBeers = () =>
  useQuery({
    queryKey: ["beer"],
    queryFn: () =>
      axios.get("localhost:3001/api/beers").then((response) => response.data),
  });

export default useBeers;

useProductData.js

import { useState } from "react";

function useProductData(products) {
  const brands = products.map(({ id, brand }) => ({
    id,
    brand,
  }));
  const prices = products.map((item) => item.price);
  const min = Math.min(...prices);
  const max = Math.max(...prices);

  const title = "Crowlers"; // TODO

  const uniqueBrands = brands.filter(
    (item, index, self) =>
      index === self.findIndex((t) => t.brand === item.brand)
  );

  const [filteredValues, setFilteredValues] = useState([min, max]);
  const [checkedBrands, setCheckedBrands] = useState({});

  const brandFilterData = { uniqueBrands, checkedBrands, setCheckedBrands };
  const priceFilterData = { min, max, filteredValues, setFilteredValues };

  const isVoucher = products.every((product) =>
    product.name.includes("Voucher")
  );

  return {
    filteredValues,
    checkedBrands,
    products,
    title,
    brandFilterData,
    priceFilterData,
    isVoucher,
  };
}

export default useProductData;

I tried changing the order of lines to execute useProductData only if !isLoading like this but it didn`t work as i expected, i had an error:
React Hook "useProductData" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

const { data: products, isLoading } = useBeers();

  if (isLoading) return <Spinner />;

  const productData = useProductData(products);

2

Answers


  1. To resolve this issue, ensure that your hooks are called unconditionally, meaning they’re called in the same order and at the top level of your functional component.
    Try like this :

    import { Box, Spinner } from "@chakra-ui/react";
    import ProductPage from "../../components/Products/ProductPage";
    import useBeers from "../../hooks/useBeers";
    import useProductData from "../../hooks/useProductData";
    
    const Catalogue = () => {
      const { data: products, isLoading } = useBeers();
    
      if (isLoading) return <Spinner />;
    
      // Move useProductData inside the component body
      const productData = useProductData(products);
    
      return (
        <Box>
          {products && <ProductPage productData={productData} />}
        </Box>
      );
    };
    
    export default Catalogue;
    
    Login or Signup to reply.
  2. Why not

    const getStuffFromProducts = (products) => {
      const brands = products.map(({ id, brand }) => ({
        id,
        brand,
      }));
      const prices = products.map((item) => item.price);
      const min = Math.min(...prices);
      const max = Math.max(...prices);
    
      const title = "Crowlers"; // TODO
    
      const uniqueBrands = brands.filter(
        (item, index, self) =>
          index === self.findIndex((t) => t.brand === item.brand)
      );
    
      return {min, max, uniqueBrands};
    }
    function useProductData(products) {
      const {min, max, uniqueBrands} = getStuffFromProducts(products)
    
      const [filteredValues, setFilteredValues] = useState([min, max]);
      const [checkedBrands, setCheckedBrands] = useState({});
    
      const brandFilterData = { uniqueBrands, checkedBrands, setCheckedBrands };
      const priceFilterData = { min, max, filteredValues, setFilteredValues };
    
      const isVoucher = products.every((product) =>
        product.name.includes("Voucher")
      );
    
      useEffect(() => {
        const {min, max} = getStuffFromProducts(products);
        setFilteredValues([min, max])
    
      }, [products])
    
      return {
        filteredValues,
        checkedBrands,
        products,
        title,
        brandFilterData,
        priceFilterData,
        isVoucher,
      };
    }
    

    with a bit of a cleanup ofc, but that’s the gist of it

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search