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 page1. 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