skip to Main Content

I am unable to filter and sort at the same time. I will be getting products from axios get call and create a copy of it into filteredProducts initially. Then i will be clicking on sort and discount filter options. Sorting is working fine here but not together with discount filter. How can i achieve this together inside useEffect ?

    const [filteredProducts, setFilteredProducts] = useState([]);

    const handleSort = (sortByValue) => {
        return filteredProducts.sort((a, b) => {
            switch(sortByValue) {
                case 'new' : return parseISO(b.created_at) - parseISO(a.created_at);
                case 'discount' : return b.discount - a.discount;
                case 'price_desc' : return b.price - a.price;
                case 'price_asc' : return a.price - b.price;
                default : return a.name.localeCompare(b.name);
            }
        })
    }
    
    const discount = queryParams.get('discount')
    const sortBy = queryParams.get('sort')

    const handleDiscountFilters = (filterResults) => {
        if(discount) {
            return filteredProducts.filter((product) => product.discount > discount)
        } else {
            return products
        }
    }
    
    // Here i want to call the different filter functions first and then call sort function. 
    useEffect(() => {
        let filterResults = [...filteredProducts];
        filterResults = handleDiscountFilters(filterResults)
        filterResults = handleSort(sortBy)
        setFilteredProducts(filterResults);
    }, [filteredProducts, handleDiscountFilters, handleSort, sortBy])

3

Answers


  1. You have to pass the array to the handleSort function. Otherwise you sort the initial array, not the filtered one

    const handleSort = (products, sortByValue) => {
      return products.sort((a, b) => {
        switch (sortByValue) {
          case "new":
            return parseISO(b.created_at) - parseISO(a.created_at);
          case "discount":
            return b.discount - a.discount;
          case "price_desc":
            return b.price - a.price;
          case "price_asc":
            return a.price - b.price;
          default:
            return a.name.localeCompare(b.name);
        }
      });
    };
    
    useEffect(() => {
      let filterResults = [...filteredProducts];
      filterResults = handleDiscountFilters(filterResults);
      filterResults = handleSort(filterProducts, sortBy);
      setFilteredProducts(filterResults);
    }, [filteredProducts, handleDiscountFilters, handleSort, sortBy]);
    
    Login or Signup to reply.
  2. There are a few problems with your code. The first is that you are modifying and setting filterResults in the same useEffect hook, which means you will have an infinite loop. Every time you call setFiltereddProducts the reference value of filteredProducts changes, which will cause your useEffect to fire again.

    Next, similar to how you pass the filterResults into handleDiscountFilters, you also need to do that for handleSort. Right now handleSort is reading from the top-level filteredProducts, which isn’t being impacted by your filters:

      let filterResults = [...filteredProducts];
      filterResults = handleDiscountFilters(filterResults)
      filterResults = handleSort(filterResults, sortBy)
    

    But, except for the network call, you really don’t need a useEffect at all for this:

    const [products, setProducts] = useState([]);
    
    useEffect(async () => {
        const response = await axios.get("someUrl");
        setProducts(response.data);
    }, []);
    
    const discount = queryParams.get('discount');
    const sortBy = queryParams.get('sort');
    
    const shouldFilterProduct = (product) => {
        if (discount) {
            return product.discount > discount;
        } else {
            return false;
        }
    };
    
    const sortProduct = (a, b) => {
        switch(sortByValue) {
            case 'new' : return parseISO(b.created_at) - parseISO(a.created_at);
            case 'discount' : return b.discount - a.discount;
            case 'price_desc' : return b.price - a.price;
            case 'price_asc' : return a.price - b.price;
            default : return a.name.localeCompare(b.name);
        }
    }
    
    const filteredAndSortedProducts = products
        .filter(filterProduct)
        .sort(sortProduct);
    
    return (
        <div>
            {filteredAndSortedProducts.map(product => {
                return <div>{product.name}</div>;
            })}
        </div>
    );
    
    Login or Signup to reply.
  3. The problem is that handleSort uses the value declared in the first line of the code, so the value of filterResults assigned from the return value of handleDiscountFilters is lost, you are always using the unfiltered data.

    So, to correct that I would add a parameter to the handleSort function, and use that parameter, instead of the const declared in the first line:

        const [filteredProducts, setFilteredProducts] = useState([]);
    
        const handleSort = (values, sortByValue) => {
            return values.sort((a, b) => {
                switch(sortByValue) {
                    case 'new' : return parseISO(b.created_at) - parseISO(a.created_at);
                    case 'discount' : return b.discount - a.discount;
                    case 'price_desc' : return b.price - a.price;
                    case 'price_asc' : return a.price - b.price;
                    default : return a.name.localeCompare(b.name);
                }
            })
        }
        
        const discount = queryParams.get('discount')
        const sortBy = queryParams.get('sort')
    
        const handleDiscountFilters = (filterResults) => {
            if(discount) {
                return filteredProducts.filter((product) => product.discount > discount)
            } else {
                return products
            }
        }
        
        // Here i want to call the different filter functions first and then call sort function. 
        useEffect(() => {
            let filterResults = [...filteredProducts];
            filterResults = handleDiscountFilters(filterResults)
            filterResults = handleSort(filterResults, sortBy)
            setFilteredProducts(filterResults);
        }, [filteredProducts, handleDiscountFilters, handleSort, sortBy])
    

    That’s about why the filtering is being ignored, but there are other problems, I think: isn’t this being rendered continously in a loop? Because the handle___ functions are new ones every render, so the useEffect will be invoked also in every render and useEffect is always invoking setFilteredProducts, so it will trigger another render, so it never stops rendering. Also, if you set the value after the first render the filtered data would remain filtered (the values filtered out will be lost). I would have a state-associated variable for the source unfiltered data and another derived from it for the filtered data.

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