How to Implement Sorting and Filtering in a React (Next.js) App with TypeScript

Overview

In this blog, we’ll walk you through how to create a dynamic sorting and filtering UI using checkboxes and a dropdown menu in a Next.js application with TypeScript. This is especially useful for product listing pages or educational content directories where users want to find results quickly based on level or sort preference.

We’ll implement:

  • Data Fetching from a mock API
  • Level-Based Filtering using checkboxes
  • Sorting using a dropdown
  • Clean UI Logic using React Hooks (useState, useEffect)

Folder Structure

/components
  └── ProductCard.tsx
/data
  └── productData.ts
/pages
  └── Category
      └── index.tsx  <-- Our main page

1. Create the Product Interface

interface Product {
  id: number;
  title: string;
  author: string;
  tags: string[];
  price: number;
  image_url: string;
  level?: string;
  words?: number;
  publish_date?: string;
  keywords?: string;
}

2. Fetch Products from a Data File or API

You can use a mock fetchProducts function to simulate real API calls:

const fetchProducts = async (): Promise<Product[]> => {
  return [
    {
      id: 1,
      title: "Atomic Habits",
      author: "James Clear",
      level: "Introductory",
      price: 499,
      image_url: "https://link-to-image.com/book1.jpg",
      tags: ["habit", "self-help"],
      publish_date: "2021-01-15",
    },
    // More products...
  ];
};

3. Sorting and Filtering Logic

Inside your Category page, we manage state using hooks:

const [products, setProducts] = useState<Product[]>([]);
const [selectedLevels, setSelectedLevels] = useState<string[]>([]);
const [sortOption, setSortOption] = useState<string>("");

3.1 Handle Level Filtering:
const handleLevelChange = (level: string) => {
  setSelectedLevels((prev) =>
    prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level]
  );
};

3.2 Filter Logic:
const filteredProducts = products.filter((product) => {
  if (selectedLevels.length === 0) return true;
  return product.level && selectedLevels.includes(product.level);
});

3.3 Sorting Logic:
const sortedProducts = [...filteredProducts].sort((a, b) => {
  switch (sortOption) {
    case "Author Ascending":
      return a.author.localeCompare(b.author);
    case "Date Published Descending":
      return (b.publish_date || "").localeCompare(a.publish_date || "");
    // Add more cases
    default:
      return 0;
  }
});

4. Build the UI

4.1 Level Filter Sidebar:
<div className="cat-left">
  <h2>Level</h2>
  {["Introductory", "Intermediate", "Advanced"].map((level) => (
    <div key={level}>
      <input
        type="checkbox"
        value={level}
        id={level}
        onChange={() => handleLevelChange(level)}
        checked={selectedLevels.includes(level)}
      />
      <label htmlFor={level}>{level}</label>
    </div>
  ))}
</div>

4.2 Sorting Dropdown:
<select onChange={(e) => setSortOption(e.target.value)} value={sortOption}>
  <option value="">Sort by</option>
  <option value="Author Ascending">Author Ascending</option>
  <option value="Date Published Descending">Date Published Descending</option>
</select>

5. Display Filtered and Sorted Products

{sortedProducts.length === 0 ? (
  <p>No products found for selected filters.</p>
) : (
  sortedProducts.map((item) => <ProductCard key={item.id} product={item} />)
)}

Optional: CSS Classes

Make sure you add styles in Category.css:

.cat-main {
  display: flex;
}
.cat-left {
  width: 20%;
}
.cat-right {
  width: 80%;
  display: flex;
  flex-wrap: wrap;
}

Final Result

You now have:

  • Checkbox filters based on level
  • A sorting dropdown for author, title, publish_date
  • Fully working filtered + sorted UI

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top