Introduction
In modern e-commerce applications, user experience is everything. Users expect to filter through hundreds of products instantly without irritating page reloads. A lagging or poorly designed filter UI can directly impact conversion rates.
In this tutorial, we will build a production-ready, highly responsive Dynamic Product Filter Component from scratch using React (with state optimization) and Tailwind CSS for slick, modern styling.
Step 1: The Product Data Structure
First, let's define our mock product database structure. Create a file named data.js or keep it inside your component:
javascript
export const PRODUCTS_DATA = [
{ id: 1, name: "UltraFit Running Shoes", category: "Footwear", price: 120, rating: 4.8 },
{ id: 2, name: "Pro-Grip Training Gloves", category: "Accessories", price: 35, rating: 4.5 },
{ id: 3, name: "AirWeave Sports Hoodie", category: "Apparel", price: 75, rating: 4.6 },
{ id: 4, name: "Pulse Smart Fitness Watch", category: "Electronics", price: 240, rating: 4.9 },
{ id: 5, name: "Apex Cushion Sneakers", category: "Footwear", price: 150, rating: 4.2 },
{ id: 6, name: "Thermal Hydro Flask", category: "Accessories", price: 45, rating: 4.7 },
];
export const CATEGORIES = ["All", "Footwear", "Apparel", "Accessories", "Electronics"];
Step 2: Implementing the Core Filter Component
We will utilize the useMemo hook from React to ensure maximum performance. This caches the filtered results and only recalculates them when our criteria actually change, preventing unnecessary re-renders.
Here is the complete code for ProductFilter.jsx:
import React, { useState, useMemo } from 'react';
const PRODUCTS_DATA = [
{ id: 1, name: "UltraFit Running Shoes", category: "Footwear", price: 120, rating: 4.8 },
{ id: 2, name: "Pro-Grip Training Gloves", category: "Accessories", price: 35, rating: 4.5 },
{ id: 3, name: "AirWeave Sports Hoodie", category: "Apparel", price: 75, rating: 4.6 },
{ id: 4, name: "Pulse Smart Fitness Watch", category: "Electronics", price: 240, rating: 4.9 },
{ id: 5, name: "Apex Cushion Sneakers", category: "Footwear", price: 150, rating: 4.2 },
{ id: 5, name: "Thermal Hydro Flask", category: "Accessories", price: 45, rating: 4.7 }
];
const CATEGORIES = ["All", "Footwear", "Apparel", "Accessories", "Electronics"];
export default function ProductFilter() {
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All');
const [maxPrice, setMaxPrice] = useState(300);
const [sortBy, setSortBy] = useState('featured');
const filteredProducts = useMemo(() => {
let result = [...PRODUCTS_DATA];
if (searchQuery.trim() !== '') {
result = result.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()));
}
if (selectedCategory !== 'All') {
result = result.filter(p => p.category === selectedCategory);
}
result = result.filter(p => p.price <= maxPrice);
if (sortBy === 'price-low') result.sort((a, b) => a.price - b.price);
else if (sortBy === 'price-high') result.sort((a, b) => b.price - a.price);
else if (sortBy === 'rating') result.sort((a, b) => b.rating - a.rating);
return result;
}, [searchQuery, selectedCategory, maxPrice, sortBy]);
return (
<div className="min-h-screen bg-slate-50 text-slate-800 p-6 md:p-12">
<div className="max-w-6xl mx-auto">
<header className="mb-8">
<h1 className="text-3xl font-bold text-slate-900">Discover Products</h1>
<p className="text-slate-500">Filter and find exactly what you need in real-time.</p>
</header>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="bg-white p-6 rounded-2xl shadow-sm border border-slate-100 h-fit space-y-6">
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2">Search</label>
<input
type="text"
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
placeholder="Search products..."
className="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
/>
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2">Category</label>
<div className="flex flex-wrap lg:flex-col gap-2">
{CATEGORIES.map(category => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className="px-3 py-1.5 text-xs font-medium rounded-lg transition"
>
{category}
</button>
))}
</div>
</div>
<div>
<div className="flex justify-between items-center mb-2">
<label className="text-sm font-semibold text-slate-700">Max Price</label>
<span className="text-sm font-bold text-blue-600">${maxPrice}</span>
</div>
<input
type="range"
min="30"
max="300"
value={maxPrice}
onChange={e => setMaxPrice(Number(e.target.value))}
className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-blue-600"
/>
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2">Sort By</label>
<select
value={sortBy}
onChange={e => setSortBy(e.target.value)}
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
>
<option value="featured">Featured</option>
<option value="price-low">Price: Low to High</option>
<option value="price-high">Price: High to Low</option>
<option value="rating">Highest Rated</option>
</select>
</div>
</div>
<div className="lg:col-span-3">
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredProducts.map(product => (
<div key={product.id} className="bg-white rounded-2xl border border-slate-100 p-5 flex flex-col justify-between shadow-sm">
<div>
<span className="text-xs font-semibold text-blue-600 bg-blue-50 px-2.5 py-1 rounded-md uppercase">
{product.category}
</span>
<h3 className="font-bold text-slate-800 text-lg mt-2">{product.name}</h3>
</div>
<div className="flex justify-between items-center mt-6 pt-4 border-t border-slate-100">
<span className="text-xl font-extrabold text-slate-900">${product.price}</span>
<span className="text-amber-500 font-bold text-sm">★ {product.rating}</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}
Conclusion
By leveraging optimization hooks, we ensure that sorting and filtering are computed only when dependencies update, maintaining a rock-solid user experience. This architecture scales perfectly for e-commerce needs.
Top comments (0)