E-Commerce Dashboard
A complete e-commerce catalog dashboard example, combining multiple operator families, React pagination, and cache invalidation into one scenario.
Product Dataset
A typed Product[] catalog that the following expressions filter:
interface Product {
id: number;
name: string;
category: string;
price: number;
rating: number;
inStock: boolean;
tags: string[];
releasedAt: Date;
}
const products: Product[] = [
{ id: 1, name: 'Mechanical Keyboard', category: 'electronics', price: 129, rating: 4.7, inStock: true, tags: ['wireless', 'mechanical'], releasedAt: new Date('2025-08-12') },
{ id: 2, name: 'Noise-Cancelling Headphones', category: 'electronics', price: 249, rating: 4.8, inStock: true, tags: ['wireless', 'over-ear'], releasedAt: new Date('2025-09-30') },
{ id: 3, name: 'Ergonomic Office Chair', category: 'furniture', price: 399, rating: 4.5, inStock: false, tags: ['mesh', 'adjustable'], releasedAt: new Date('2025-06-03') },
{ id: 4, name: 'Laptop Backpack', category: 'accessories', price: 79, rating: 4.3, inStock: true, tags: ['waterproof', 'travel'], releasedAt: new Date('2025-07-21') },
{ id: 5, name: 'Smart Home Hub', category: 'electronics', price: 149, rating: 4.1, inStock: true, tags: ['wireless', 'smart-home'], releasedAt: new Date('2025-10-15') },
{ id: 6, name: 'Standing Desk', category: 'furniture', price: 599, rating: 4.6, inStock: true, tags: ['adjustable', 'motorized'], releasedAt: new Date('2025-05-11') },
{ id: 7, name: 'Ultrabook Laptop', category: 'electronics', price: 1249, rating: 4.9, inStock: true, tags: ['ultrabook', 'usb-c'], releasedAt: new Date('2025-11-02') },
{ id: 8, name: 'Wireless Mouse', category: 'electronics', price: 49, rating: 4.2, inStock: true, tags: ['wireless', 'compact'], releasedAt: new Date('2025-04-19') },
{ id: 9, name: 'Desk Organizer', category: 'accessories', price: 29, rating: 3.9, inStock: false, tags: ['storage', 'bamboo'], releasedAt: new Date('2025-03-08') },
{ id: 10, name: 'Portable Monitor', category: 'electronics', price: 219, rating: 4.4, inStock: true, tags: ['usb-c', 'portable'], releasedAt: new Date('2025-12-01') },
];Multi-Criteria Search
One $and expression combining a price range ($gte/$lte), an array tag match ($contains), and a regular expression on the product name:
import { filter } from '@mcabreradev/filter';
// Wireless electronics between $100 and $300
filter(products, {
$and: [
{ price: { $gte: 100 } },
{ price: { $lte: 300 } },
{ tags: { $contains: 'wireless' } }, // any 'wireless' tag
{ name: { $regex: /^(smart|noise)/i } },
],
});
// -> [
// -> { id: 2, name: 'Noise-Cancelling Headphones', price: 249, ... },
// -> { id: 5, name: 'Smart Home Hub', price: 149, ... }
// -> ]
// Same expression without the name constraint
filter(products, {
$and: [
{ price: { $gte: 100 } },
{ price: { $lte: 300 } },
{ tags: { $contains: 'wireless' } },
],
});
// -> [1, 2, 5] // Mechanical Keyboard, Noise-Cancelling Headphones, Smart Home HubFiltering by Date Range
Date values are compared by their time value, so a releasedAt bound checked with $gte/$lte uses the same instant semantics as a numeric comparison while remaining null-safe:
// Products released between 2025-07-01 and 2025-09-30 (inclusive)
const from = new Date('2025-07-01');
const to = new Date('2025-09-30T23:59:59.999');
filter(products, {
$and: [
{ releasedAt: { $gte: from } },
{ releasedAt: { $lte: to } },
],
});
// -> [
// -> { id: 1, name: 'Mechanical Keyboard', releasedAt: 2025-08-12, ... },
// -> { id: 2, name: 'Noise-Cancelling Headphones', releasedAt: 2025-09-30, ... },
// -> { id: 4, name: 'Laptop Backpack', releasedAt: 2025-07-21, ... }
// -> ]
// Dates at the same instant compare as equal
filter([{ t: new Date('2025-07-01T00:00:00Z') }, { t: new Date('2025-07-02T00:00:00Z') }], {
t: { $eq: new Date('2025-07-01T00:00:00Z') },
});
// -> [{ t: 2025-07-01T00:00:00Z }]Note: Dates compare by their time value, not by object identity. Two
Dateinstances created for the same instant are treated as equal by the range operators.
Paginated Results with usePaginatedFilter
The React binding filters and paginates in one call. currentPage starts at 1, and goToPage/nextPage/previousPage clamp to the valid range:
import { usePaginatedFilter } from '@mcabreradev/filter/react';
const Dashboard = ({ products }: { products: Product[] }) => {
const [pageSize, setPageSize] = useState(3);
const {
filtered,
isFiltering,
currentPage,
totalItems,
totalPages,
nextPage,
previousPage,
goToPage,
} = usePaginatedFilter<Product>(products, { inStock: { $eq: true } }, pageSize);
// totalItems is the number of matching products (all in-stock items)
// totalPages is ceil(totalItems / pageSize)
// currentPage starts at 1; goToPage clamps to [1, totalPages]
return (
<div>
<ProductGrid products={filtered} />
<Pagination
current={currentPage}
total={totalPages}
onNext={nextPage}
onPrevious={previousPage}
onGoTo={goToPage}
/>
<PageSizeSelector value={pageSize} onChange={setPageSize} />
{isFiltering && <Spinner />}
</div>
);
};The hook owns the page position and clamps every
goToPage/nextPage/previousPagecall to the valid range, so you don't keep your ownpagestate — readcurrentPageand drive navigation with the returned actions.setPageSizeresets to page 1.
With the seed dataset, { inStock: { $eq: true } } matches ids [1, 2, 4, 5, 6, 7, 8, 10], so with pageSize of 3:
// -> currentPage: 1, totalItems: 8, totalPages: 3
// -> page 1: ids [1, 2, 4] (first 3 in-stock products)
// -> page 2: ids [5, 6, 7]
// -> page 3: ids [8, 10]
// -> goToPage(9) -> currentPage: 3 (clamped)
// -> previousPage -> currentPage: 2
// -> setPageSize(6) -> currentPage: 1, totalPages: 2Cache Invalidation After a Data Refresh
The filter cache is enabled per-call with the enableCache option. The result cache is keyed on the array reference plus the expression hash — so replacing the dataset with a brand-new array won't serve stale entries. The case that needs clearFilterCache() is mutating the same array reference in place (then re-filtering within the cache TTL), because mutation doesn't change the cache key:
import { filter, clearFilterCache } from '@mcabreradev/filter';
// A mutable catalog that refreshes in place, keeping the same array reference
const catalog: Product[] = [
{ id: 1, name: 'Mechanical Keyboard', price: 129, tags: ['wireless', 'mechanical'], inStock: true, rating: 4.7, category: 'electronics', releasedAt: new Date('2025-08-12') },
{ id: 2, name: 'Noise-Cancelling Headphones', price: 249, tags: ['wireless', 'over-ear'], inStock: true, rating: 4.8, category: 'electronics', releasedAt: new Date('2025-09-30') },
{ id: 3, name: 'Desk Organizer', price: 29, tags: ['storage', 'bamboo'], inStock: true, rating: 3.9, category: 'accessories', releasedAt: new Date('2025-03-08') },
];
// First call primes the cache with a result keyed to this `catalog` reference
console.log(filter(catalog, { price: { $gte: 200 } }, { enableCache: true }));
// -> [{ id: 2, name: 'Noise-Cancelling Headphones', price: 249, ... }]
// Mutate in place (same reference) and re-filter within the cache TTL
catalog[2].price = 300; // e.g. a price update from a live refresh
// The cache would still return the old entry unless we invalidate it
clearFilterCache();
console.log(filter(catalog, { price: { $gte: 200 } }, { enableCache: true }));
// -> [{ id: 2, ... }, { id: 3, ... }] // fresh, includes the updated itemNote:
clearFilterCache()is imported from the package root (@mcabreradev/filter) and clears both the result cache and the memoized predicate/regex caches. Call it after any in-place mutation of the same array reference; replacing the array with a fresh reference needs no invalidation.
Related Resources
- E-Commerce Examples — per-feature snippets for the same domain
- Recipes — reusable patterns for real-world filtering
- React Integration — the
useFilter/usePaginatedFilterfamily