Search code examples
javascriptreactjsreact-hooksuse-effectuse-state

React useState hook causes infinite rendering


I'm listing all products according to the search query. I store all brands and sellers information of listed products in useState hook. Which until this point it works. Now I want to store all categories in useState hook.

listProducts are the listed version of results. Thoose results can be sorted or filtered seperately by user. And defaultProducts will not be effected. defaultProducts is what I get from the database.

  const { query } = useParams();
  const [products, setProducts] = useState([]);
  const [listProducts, setListProducts] = useState([]);
  const [brands, setBrands] = useState([]);
  const [sellers, SetSellers] = useState([]);
  const [Categories, setCategories] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    setIsLoading(true);
    
    axios
      .get(`/api/product/search/${query}`)
      .then((res) => res.data)
      .then((data) => {
        setIsLoading(false);
        setProducts(data.products);
        setListProducts(data.products);

        if (data.products.length < 1) {
          setProducts(data.brands);
          setListProducts(data.brands);
        }
      });
  }, [query]);

  useEffect(() => {
    let brandSet = new Set();
    let sellerSet = new Set();
    let categoriesSet = new Set();

    products.forEach((item) => {
      categoriesSet.add(item.category);
      categoriesSet.add(item.subCategory);
      brandSet.add(item.brand);
      sellerSet.add(item.shop.companyName);
    });

    setCategories([...categoriesSet]);
    setBrands([...brandSet]);
    SetSellers([...sellerSet]);
  }, [products]);

And I finally render ProductFilters component.

 <ProductFilters
   DefaultProducts={products}
   ListProducts={listProducts}
   Brands={brands}
   Sellers={sellers}
   Categories={Categories}
   setListProducts={setListProducts}
   Brand={brand}
 />

In ProductFilters.js

<FilterTypeSection className="mt-3">
        <FilterType>Categories</FilterType>
        <CategoriesSection>
          {Categories.map((categoryItem, index) => {
            return (
              <CategoryItem
                key={index}
                onClick={
                  category === categoryItem
                    ? setCategory("")
                    : setCategory(categoryItem)
                }
                style={
                  categoryItem === category
                    ? { fontWeight: "bold", color: "black" }
                    : { fontWeight: "normal", color: "var(--text-muted)" }
                }
              >
                {categoryItem}
              </CategoryItem>
            );
          })}
        </CategoriesSection>
</FilterTypeSection>

I listed sellers and brands data like above and they worked. But when I added this code I got this error:

Unhandled Rejection (Error): Too many re-renders. React limits the number of renders to prevent an infinite loop.
▶ 18 stack frames were collapsed.
(anonymous function)
src/pages/searchResultsPage.js:162
  159 |   sellerSet.add(item.shop.companyName);
  160 | });
  161 | 
> 162 | setCategories([...categoriesSet]);
      | ^  163 | setBrands([...brandSet]);
  164 | SetSellers([...sellerSet]);
  165 | 

Even if I set a default value for Categories prop like Categories=["a","b","c"] I still get same error.


Solution

  • Issue

    In your onClick function you are doing a function call setCategory("") . But what you need is a just a function signature which can be called at a later time when the user clicks it .

    so change your onClick as an inline function.

    onClick={() => {
      category === categoryItem
      ? setCategory("")
      : setCategory(categoryItem)
    }