Shopify product filtering by category not working - filter function error

I’m trying to add category filtering to my Shopify store but running into issues. Users should be able to click buttons to filter items by category type.

Here’s my API helper file utils/shopify-api.js:

export async function fetchCollectionItems() {
  const query = `
  {
    collectionByHandle(handle: "homepage") {
      title
      products(first: 30) {
        edges {
          node {
            id
            title
            productType
            handle
          }
        }
      }
    }
  }`

  const result = await ShopifyAPI(query)
  const items = result.data.collectionByHandle.products.edges ? result.data.collectionByHandle.products.edges : []
  return items
}

export function filterByCategory(category) {
  let filtered = fetchCollectionItems().filter(item => item.node.productType === category);
  return filtered;
}

I also have a function to get all categories:

export async function getAllCategories() {
  const query = 
  `{
    shop {
      products(first:200, query:"-product_type:''") {
        edges {
            node {
              productType
          }
        }
      }
    }
  }`

  const result = await ShopifyAPI(query)
  const categories = result.data.shop.products ? result.data.shop.products.edges : []
  const uniqueCategories = Array.from(new Set(categories));
  return uniqueCategories
}

My main component store.js renders the filter buttons:

import { fetchCollectionItems, getAllCategories, filterByCategory } from "../utils/shopify-api"
import React, { useState, useEffect } from "react";

export default function Store({ items, categories }) {
  const [filteredItems, setFilteredItems] = useState(null);

  useEffect(() => {
    setFilteredItems(fetchCollectionItems());
  }, []);

  function handleFilter(e) {
    let selectedType = e.target.value;
    selectedType !== "all"
      ? setFilteredItems(filterByCategory(selectedType))
      : setFilteredItems(fetchCollectionItems());
  }

  return (
    <div>
      <div>
         {categories &&
        categories.map((cat, idx) => (
          <button className="filter-btn" key={idx} value={cat.node.productType} onClick={handleFilter}>
             {cat.node.productType}
          </button>
        ))}
      {filteredItems &&
          filteredItems.map(item => (
            <div key={item.node.id}>
              <h3>{item.node.title}</h3>
            </div>
          ))}
      </div>
    </div>
  )
}

export async function getStaticProps() {
    const items = await fetchCollectionItems()
    const categories = await getAllCategories()
  
    return {
      props: { 
        items,
        categories,
      },
    }
  }

When I click the filter buttons I get this error: TypeError: fetchCollectionItems(...).filter is not a function

How can I fix this filtering functionality? The buttons should filter the displayed products when clicked.

The problem is fetchCollectionItems returns a promise, but you’re trying to filter it synchronously. Just use the items prop you already have - no need to refetch. Change your useEffect to setFilteredItems(items) and in handleFilter do items.filter(item => item.node.productType === selectedType) instead of calling filterByCategory. You’ve got the data already, so skip the extra API calls.

You’re mixing async and sync operations. Your filterByCategory function calls fetchCollectionItems() which returns a Promise, but then tries to use .filter() on it right away without awaiting the result.

Skip the async mess and just filter client-side with the data you already have. Replace your useEffect and handleFilter like this:

const [filteredItems, setFilteredItems] = useState(items);

// Remove the useEffect entirely

function handleFilter(e) {
  const selectedType = e.target.value;
  const result = selectedType === "all" 
    ? items 
    : items.filter(item => item.node.productType === selectedType);
  setFilteredItems(result);
}

This cuts out the async headache and runs way faster since you’re not hammering the API every time. Filtering happens instantly on data that’s already loaded at build time.

Your filterByCategory function is trying to call .filter() on a Promise, not an array. Since fetchCollectionItems() is async, it returns a Promise that needs awaiting.

But here’s a better approach - don’t make API calls every time you filter. Just use the items you already fetched in getStaticProps:

function handleFilter(e) {
  let selectedType = e.target.value;
  if (selectedType !== "all") {
    const filtered = items.filter(item => item.node.productType === selectedType);
    setFilteredItems(filtered);
  } else {
    setFilteredItems(items);
  }
}

Also initialize your state with the items prop:

const [filteredItems, setFilteredItems] = useState(items);

This gets rid of filterByCategory completely and stops unnecessary API calls. Your filtering will be way faster since it’s working with data already in memory.