Filters and Sort
The Filters and Sort Service is a lightweight, framework-agnostic service for managing filter state, sorting, pagination, and search with automatic persistence and RxJS Observables for reactive updates.
Key Features
- ✅ Zero-Dependency Core - Pure TypeScript, works everywhere
- ✅ Type-Safe - Full TypeScript support with type inference
- ✅ Simple State Management - Key-value filter objects and single-field sorting
- ✅ Automatic Persistence - Built-in localStorage persistence with custom adapter support
- ✅ Reactive Updates - RxJS Observable-based state management
- ✅ Debounced State - Optional debouncing for performance optimization
- ✅ Pagination Support - Built-in page/limit state management
- ✅ Search Queries - Query string support for full-text search
- ✅ Easy React Integration - Works seamlessly with React hooks and effects
Installation
# Install the filters and sort package
npm install @codella-software/utils
# Or from JSR
npx jsr add @codella-software/utils
Quick Start
Basic Filtering (Core)
import { FiltersAndSortService } from '@codella-software/utils';
// Define filter interface
interface UserFilters {
role?: string;
name?: string;
}
// Create filter service
const filterService = new FiltersAndSortService<UserFilters>({
storageKey: 'user-filters',
defaultFilters: { role: '', name: '' }
});
// Subscribe to filter state changes
filterService.getState().subscribe(state => {
console.log('Current filters:', state.filters);
console.log('Current sort:', state.sort);
console.log('Current pagination:', state.pagination);
});
// Update filters
filterService.setFilters({ role: 'User' });
// Add/update another filter
filterService.setFilters({ name: 'B' });
// Combined filters are automatically merged
const currentState = filterService.getCurrentState();
console.log('Applied filters:', currentState.filters);
// Output: { role: 'User', name: 'B' }
// Remove specific filter
filterService.removeFilter('name');
// Clear all filters
filterService.clearFilters();
Basic Filtering (React)
import { useFilters } from '@codella-software/react';
export function UserFilter() {
const {
filteredData,
setFilters,
removeFilter,
filters,
clearFilters
} = useFilters<{ role: string }>({
role: ''
});
return (
<div>
<button onClick={() => setFilters({ role: 'Admin' })}>
Admin Only
</button>
<button onClick={() => setFilters({ role: 'User' })}>
Users Only
</button>
<button onClick={clearFilters}>Clear Filters</button>
<ul>
{filteredData.map(user => (
<li key={user.id}>{user.name} ({user.role})</li>
))}
</ul>
</div>
);
}
Service Methods
The FiltersAndSortService provides simple state management for filters, sorting, and pagination:
Setting Filters
interface Filters {
status?: string;
category?: string;
dateRange?: [string, string];
}
const service = new FiltersAndSortService<Filters>({
storageKey: 'my-filters',
defaultFilters: { status: '', category: '' }
});
// Set or update filters (merged with existing)
service.setFilters({
status: 'active',
category: 'electronics'
});
// Get current filters
const { filters } = service.getCurrentState();
console.log(filters); // { status: 'active', category: 'electronics' }
Removing and Clearing Filters
// Remove specific filter
service.removeFilter('category');
// Clear all filters (reset to defaults)
service.clearFilters();
// Get state after changes
const state = service.getCurrentState();
console.log(state.filters); // Reflects changes
Sorting
// Set sort by field
service.setSort('name');
// Get current sort
const { sort } = service.getCurrentState();
console.log(sort); // { fieldName: 'name', direction: 'asc' }
// Toggle sort direction by calling again
service.setSort('name'); // Changes to 'desc'
service.setSort('name'); // Clears sort
// Clear sort
service.setSort('');
Search/Query
// Set search query
service.setQuery('john');
// Get current query
const { query } = service.getCurrentState();
console.log(query); // 'john'
// Clear query
service.setQuery('');
Pagination
// Set pagination
service.setPagination(1, 20); // page, limit
// Get current pagination
const { pagination } = service.getCurrentState();
console.log(pagination); // { page: 1, limit: 20 }
Practical Examples
Filter Multiple Fields
interface ProductFilters {
category?: string;
priceMin?: number;
priceMax?: number;
inStock?: boolean;
}
const filterService = new FiltersAndSortService<ProductFilters>({
storageKey: 'product-filters',
defaultFilters: {
category: '',
priceMin: undefined,
priceMax: undefined,
inStock: false
}
});
// Apply multiple filters at once
filterService.setFilters({
category: 'Electronics',
priceMin: 100,
priceMax: 500,
inStock: true
});
// Update just one filter (others remain)
filterService.setFilters({ priceMin: 200 });
// Result: category='Electronics', priceMin=200, priceMax=500, inStock=true
Date Range Filtering
interface DateFilters {
startDate?: string;
endDate?: string;
}
const filterService = new FiltersAndSortService<DateFilters>({
storageKey: 'date-filters',
defaultFilters: { startDate: '', endDate: '' }
});
const today = new Date().toISOString().split('T')[0];
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
.toISOString()
.split('T')[0];
filterService.setFilters({
startDate: sevenDaysAgo,
endDate: today
});
// When filtering arrays, your UI/application logic handles date comparison
Search with Filters
const filterService = new FiltersAndSortService<{ category?: string }>({
storageKey: 'search-filters',
defaultFilters: { category: '' }
});
// Set search query (full-text search in UI layer)
filterService.setQuery('laptop');
// Also filter by category
filterService.setFilters({ category: 'Electronics' });
// Get current state
const state = filterService.getCurrentState();
console.log(state.query); // 'laptop'
console.log(state.filters); // { category: 'Electronics' }
Sorting
The service manages sort state with a single-field sort at a time. Clicking the same field twice toggles between ascending, descending, and no sort.
Basic Sorting
import { FiltersAndSortService } from '@codella-software/utils';
const sortService = new FiltersAndSortService<{ name?: string }>({
storageKey: 'sort-state',
defaultFilters: {}
});
// Sort by name ascending
sortService.setSort('name');
// State: { fieldName: 'name', direction: 'asc' }
// Click again - toggles to descending
sortService.setSort('name');
// State: { fieldName: 'name', direction: 'desc' }
// Click again - clears sort
sortService.setSort('name');
// State: undefined
// Clear sort explicitly
sortService.setSort('');
// State: undefined
// Subscribe to sort changes
sortService.getState().subscribe(state => {
console.log('Current sort:', state.sort);
// Output: { fieldName: 'name', direction: 'asc' }
});
Sorting with Pagination
interface DataFilters {
status?: string;
}
const service = new FiltersAndSortService<DataFilters>({
storageKey: 'table-state',
defaultFilters: { status: '' }
});
// Apply filters
service.setFilters({ status: 'active' });
// Set sort
service.setSort('createdDate');
// Go to page 1 when sorting
service.setPagination(1, 20);
// Get all state at once
const state = service.getCurrentState();
console.log({
filters: state.filters, // { status: 'active' }
sort: state.sort, // { fieldName: 'createdDate', direction: 'asc' }
pagination: state.pagination // { page: 1, limit: 20 }
});
React Integration
Using the Service Directly
The easiest way to use FiltersAndSortService in React is with the service directly:
import { useEffect, useState } from 'react';
import { FiltersAndSortService, FilterAndSortState } from '@codella-software/utils';
interface UserFilters {
department?: string;
status?: string;
}
export function UserTable() {
const [service] = useState(
() => new FiltersAndSortService<UserFilters>({
storageKey: 'users-table',
defaultFilters: { department: '', status: '' }
})
);
const [state, setState] = useState<FilterAndSortState<UserFilters>>(() =>
service.getCurrentState()
);
useEffect(() => {
const subscription = service.getState().subscribe(setState);
return () => subscription.unsubscribe();
}, [service]);
const handleDepartmentFilter = (dept: string) => {
service.setFilters({ department: dept });
};
const handleStatusFilter = (status: string) => {
service.setFilters({ status });
};
const handleClearFilters = () => {
service.clearFilters();
};
return (
<div>
<div className="filters">
<button onClick={() => handleDepartmentFilter('Engineering')}>
Engineering
</button>
<button onClick={() => handleDepartmentFilter('Sales')}>
Sales
</button>
<button onClick={() => handleStatusFilter('active')}>
Active Only
</button>
<button onClick={handleClearFilters}>Clear Filters</button>
</div>
<table>
<thead>
<tr>
<th onClick={() => service.setSort('name')}>Name</th>
<th onClick={() => service.setSort('department')}>Department</th>
<th onClick={() => service.setSort('createdDate')}>Created</th>
</tr>
</thead>
<tbody>
{/* Render filtered and sorted data */}
</tbody>
</table>
{/* Show active filters */}
<div className="active-filters">
<p>Active Filters: {JSON.stringify(state.filters)}</p>
<p>Sort: {state.sort ? `${state.sort.fieldName} (${state.sort.direction})` : 'None'}</p>
</div>
</div>
);
}
With useEffect for Advanced State Sync
import { useEffect, useState, useCallback } from 'react';
import { FiltersAndSortService } from '@codella-software/utils';
export function AdvancedFilterTable() {
const [service] = useState(
() => new FiltersAndSortService({
storageKey: 'advanced-table',
defaultFilters: {}
})
);
const [filters, setFilters] = useState({});
const [sort, setSort] = useState(undefined);
const [pagination, setPagination] = useState({ page: 0, limit: 20 });
useEffect(() => {
const subscription = service.getState().subscribe(state => {
setFilters(state.filters);
setSort(state.sort);
setPagination(state.pagination);
});
return () => subscription.unsubscribe();
}, [service]);
const updateFilters = useCallback((updates) => {
service.setFilters(updates);
}, [service]);
const updateSort = useCallback((fieldName) => {
service.setSort(fieldName);
}, [service]);
const goToPage = useCallback((page) => {
service.setPagination(page, pagination.limit);
}, [service, pagination]);
return (
<div>
{/* Filter controls using setFilters */}
{/* Sort headers using setSort */}
{/* Pagination using setPagination */}
</div>
);
}
State Management Pattern
Debounced State Updates
For responsive UIs with heavy filtering, debounce state updates:
import { useEffect, useState } from 'react';
import { FiltersAndSortService } from '@codella-software/utils';
export function OptimizedTable() {
const [service] = useState(
() => new FiltersAndSortService({
storageKey: 'optimized-table',
defaultFilters: {},
storageDebounceMs: 300 // Debounce storage writes
})
);
const [state, setState] = useState(() => service.getCurrentState());
useEffect(() => {
// Use debounced state for UI updates
const subscription = service.getDebouncedState(300).subscribe(setState);
return () => subscription.unsubscribe();
}, [service]);
return (
<div>
{/* Your table/list implementation */}
</div>
);
}
API Reference
FiltersAndSortService Methods
| Method | Description |
|---|---|
setFilters(filters) | Merge new filters with existing ones |
removeFilter(key) | Remove a specific filter |
clearFilters() | Clear all filters (reset to defaults) |
setSort(fieldName) | Set sort by field (toggles direction on repeated calls) |
setQuery(query) | Set search query string |
setPagination(page, limit) | Set pagination (1-based page number) |
getState() | Get Observable of state changes |
getCurrentState() | Get current state synchronously |
getDebouncedState(ms) | Get debounced state Observable |
reset() | Reset to initial state |
clearStorage() | Clear persisted storage |
State Shape
interface FilterAndSortState<T> {
filters: T; // Filter key-value pairs
sort?: {
fieldName: string; // Field name to sort by
direction: 'asc' | 'desc'; // Sort direction
};
pagination: {
page: number; // Current page (0-based)
limit: number; // Items per page
};
query: string; // Search query
}
Persistence
By default, service state is automatically persisted to localStorage using the provided storageKey. Disable with persistToStorage: false:
const service = new FiltersAndSortService({
storageKey: 'my-filters',
persistToStorage: false // Disable persistence
});
Use a custom storage adapter:
const customAdapter = {
getItem: (key) => { /* custom get */ },
setItem: (key, value) => { /* custom set */ },
removeItem: (key) => { /* custom remove */ }
};
const service = new FiltersAndSortService({
storageKey: 'my-filters',
storageAdapter: customAdapter
});
Next Steps
- See Table Builder for table integration examples
- Check Getting Started for complete end-to-end examples
- View Core Services for other utilities