useFilterSortHandler Hook
useFilterSortHandler adalah custom React hook yang powerful untuk mengelola filter dan sort conditions secara otomatis dengan support untuk URL encoding, date formatting, dan API integration.
📋 Overview
🎯 Fungsi Utama
- Universal Filter Handler: Support berbagai jenis filter dengan operator-based conditions
- Sort Management: Handle sorting untuk Mantine DataTable dan custom sort conditions
- Pagination Control: Built-in pagination state management
- Auto-processing: Otomatis convert filter objects ke query strings
- URL Encoding: Support untuk URL-safe parameter encoding
- Date Formatting: Otomatis format date values ke standard format
- Callback Support: Integrasi dengan API calls untuk real-time updates
🔄 Alur Kerja
🚀 Quick Start
Basic Usage
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
import type { DataTableSortStatus } from 'mantine-datatable';
function ProductList() {
const {
filter, // Processed filter string
sort, // Processed sort string
page, // Current page
perPage, // Items per page
setFilters, // Update filters
setSortStatus, // Update sort
setPage, // Update page
setPerPage, // Update per page
clearFilters, // Clear all filters
hasActiveFilters // Check if any filters active
} = useFilterSortHandler(
// Initial filters
{
search: '',
category: '',
status: 'active',
price_min: undefined,
price_max: undefined
},
// Initial sort
{ columnAccessor: 'name', direction: 'asc' }
);
// Use in API call
const { data, loading } = useQuery(['products', filter, sort, page, perPage], () =>
fetchProducts({ filter, sort, page, perPage })
);
return (
<div>
<SearchInput value={filters.search} onChange={(value) => setFilters({ search: value })} />
<CategorySelect value={filters.category} onChange={(value) => setFilters({ category: value })} />
<DataTable
records={data?.products}
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
page={page}
onPageChange={setPage}
recordsPerPage={perPage}
onRecordsPerPageChange={setPerPage}
totalRecords={data?.total}
/>
</div>
);
}
Advanced Configuration
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
function AdvancedUserManagement() {
const {
filter,
sort,
page,
perPage,
filters,
sortStatus,
setFilters,
setSortStatus,
updateFilter,
removeFilter,
resetToDefaults,
hasActiveFilters,
hasActiveSort
} = useFilterSortHandler(
// Initial filters
{
search: '',
role: 'user',
department: undefined,
created_after: undefined,
created_before: undefined,
is_active: true
},
// Initial sort
{ columnAccessor: 'created_at', direction: 'desc' },
// Options
{
encodeFilters: true,
encodeSort: true,
formatDateValues: true,
autoRefetch: false,
onFiltersChange: (filters, processedFilter) => {
console.log('Filters changed:', filters);
console.log('Processed filter:', processedFilter);
// Trigger API call
fetchUsers({
filter: processedFilter,
sort: sort,
page: page,
perPage: perPage
});
},
onSortChange: (sort, processedSort) => {
console.log('Sort changed:', sort);
console.log('Processed sort:', processedSort);
}
}
);
return (
<div>
<div style={{ marginBottom: '20px' }}>
<input
placeholder="Search users..."
value={filters.search}
onChange={(e) => updateFilter('search', e.target.value)}
/>
<select
value={filters.role}
onChange={(e) => updateFilter('role', e.target.value)}
>
<option value="">All Roles</option>
<option value="admin">Admin</option>
<option value="user">User</option>
</select>
<DatePicker
placeholder="Created after"
value={filters.created_after}
onChange={(date) => updateFilter('created_after', date)}
/>
{hasActiveFilters && (
<button onClick={clearFilters}>Clear Filters</button>
)}
</div>
<UserTable
users={users}
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
/>
</div>
);
}
📝 API Reference
Hook Interface
interface UseFilterSortHandlerOptions extends FilterSortOptions {
autoRefetch?: boolean;
onFiltersChange?: (filters: Record<string, unknown>, processedFilters: string) => void;
onSortChange?: (sort: DataTableSortStatus | SortCondition | null, processedSort: string) => void;
encodeFilters?: boolean; // URL encoding filters
encodeSort?: boolean; // URL encoding sort
formatDateValues?: boolean; // Format date values to YYYY-MM-DD
}
interface UseFilterSortHandlerReturn<TFilters> {
// Processed query parameters
filter?: string;
sort?: string;
page: number;
perPage: number;
// State setters
setPage: (page: number) => void;
setPerPage: (perPage: number) => void;
setFilters: (values: Partial<TFilters>) => void;
setSortStatus: (sort: DataTableSortStatus | SortCondition | null) => void;
// Internal state
filters: TFilters;
sortStatus: DataTableSortStatus | SortCondition | null;
// Actions
updateFilter: (key: string, value: unknown) => void;
removeFilter: (key: string) => void;
clearFilters: () => void;
resetToDefaults: () => void;
// Utilities
hasActiveFilters: boolean;
hasActiveSort: boolean;
filterValues: Record<string, unknown>; // Simple values for forms
}
function useFilterSortHandler<TFilters extends Record<string, unknown>>(
initialFilters: TFilters,
initialSort?: DataTableSortStatus | SortCondition | null,
options?: UseFilterSortHandlerOptions
): UseFilterSortHandlerReturn<TFilters>;
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
initialFilters | TFilters | - | Required. Initial filter state object |
initialSort | DataTableSortStatus | SortCondition | null | null | Initial sort condition |
options | UseFilterSortHandlerOptions | {} | Additional configuration options |
Return Values
| Property | Type | Description |
|---|---|---|
filter | string | undefined | Processed filter string for API calls |
sort | string | undefined | Processed sort string for API calls |
page | number | Current pagination page (starts from 1) |
perPage | number | Items per page (default: 10) |
filters | TFilters | Current filter state object |
sortStatus | DataTableSortStatus | SortCondition | null | Current sort state |
setFilters | function | Update multiple filters at once |
setSortStatus | function | Update sort status |
setPage | function | Update current page |
setPerPage | function | Update items per page |
updateFilter | function | Update single filter value |
removeFilter | function | Remove specific filter |
clearFilters | function | Reset all filters to initial values |
resetToDefaults | function | Reset both filters and sort to defaults |
hasActiveFilters | boolean | True if any non-default filters are active |
hasActiveSort | boolean | True if any sort is active |
filterValues | Record<string, unknown> | Filter values for form inputs |
🎨 Examples
E-commerce Product Filtering
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
function ProductCatalog() {
const {
filter,
sort,
page,
perPage,
filters,
setFilters,
setSortStatus,
setPage,
setPerPage,
updateFilter,
hasActiveFilters
} = useFilterSortHandler(
{
search: '',
category_id: undefined,
brand_id: undefined,
min_price: undefined,
max_price: undefined,
in_stock: undefined,
rating: undefined
},
{ columnAccessor: 'created_at', direction: 'desc' },
{
encodeFilters: true,
formatDateValues: false,
onFiltersChange: (newFilters, processedFilter) => {
// Update URL without page reload
const url = new URL(window.location);
url.searchParams.set('filter', processedFilter);
window.history.pushState({}, '', url);
}
}
);
// API integration
const { data: productsData, isLoading } = useQuery(
['products', filter, sort, page, perPage],
() => api.products.list({ filter, sort, page, perPage }),
{ keepPreviousData: true }
);
return (
<div>
{/* Filters Sidebar */}
<aside>
<SearchFilter
value={filters.search}
onChange={(value) => updateFilter('search', value)}
/>
<CategoryFilter
value={filters.category_id}
onChange={(value) => updateFilter('category_id', value)}
/>
<PriceRangeFilter
min={filters.min_price}
max={filters.max_price}
onMinChange={(value) => updateFilter('min_price', value)}
onMaxChange={(value) => updateFilter('max_price', value)}
/>
{hasActiveFilters && (
<Button onClick={() => {
clearFilters();
setPage(1); // Reset to first page
}}>
Clear All Filters
</Button>
)}
</aside>
{/* Product Grid */}
<main>
<SortControls
sortStatus={sortStatus}
onSortChange={setSortStatus}
/>
<ProductGrid
products={productsData?.products}
loading={isLoading}
/>
<Pagination
page={page}
onChange={setPage}
total={productsData?.total || 0}
perPage={perPage}
onPerPageChange={setPerPage}
/>
</main>
</div>
);
}
User Management with Complex Filters
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
import { DateRangePicker } from '@mantine/dates';
function UserManagement() {
const {
filter,
sort,
page,
perPage,
filters,
sortStatus,
updateFilter,
removeFilter,
setPage,
setSortStatus,
hasActiveFilters,
hasActiveSort
} = useFilterSortHandler(
{
// Text filters
search: '',
email: '',
// Select filters
role: undefined,
department: undefined,
status: undefined,
// Date filters
created_after: undefined,
created_before: undefined,
last_login_after: undefined,
// Boolean filters
is_active: undefined,
email_verified: undefined,
// Array filters
skills: [],
permissions: []
},
{ columnAccessor: 'created_at', direction: 'desc' },
{
encodeFilters: true,
formatDateValues: true,
onFiltersChange: async (filters, processedFilter) => {
// Debounced API call
await new Promise(resolve => setTimeout(resolve, 300));
try {
await fetchUsers({
filter: processedFilter,
sort: sort,
page: 1, // Always reset to first page on filter change
perPage: perPage
});
setPage(1);
} catch (error) {
console.error('Failed to fetch users:', error);
}
}
}
);
return (
<div>
<div className="filters-section">
{/* Search Fields */}
<Group>
<TextInput
placeholder="Search name or username"
value={filters.search}
onChange={(e) => updateFilter('search', e.target.value)}
/>
<TextInput
placeholder="Email address"
value={filters.email}
onChange={(e) => updateFilter('email', e.target.value)}
/>
</Group>
{/* Select Filters */}
<Group>
<Select
placeholder="Role"
value={filters.role}
onChange={(value) => updateFilter('role', value)}
data={[
{ value: 'admin', label: 'Admin' },
{ value: 'user', label: 'User' },
{ value: 'moderator', label: 'Moderator' }
]}
/>
<Select
placeholder="Department"
value={filters.department}
onChange={(value) => updateFilter('department', value)}
data={departmentOptions}
/>
</Group>
{/* Date Range Filters */}
<DateRangePicker
placeholder="Registration date range"
value={[
filters.created_after ? new Date(filters.created_after) : null,
filters.created_before ? new Date(filters.created_before) : null
]}
onChange={(dates) => {
updateFilter('created_after', dates[0]);
updateFilter('created_before', dates[1]);
}}
/>
{/* Boolean Filters */}
<Group>
<Checkbox
label="Active users only"
checked={filters.is_active}
onChange={(e) => updateFilter('is_active', e.target.checked)}
/>
<Checkbox
label="Email verified"
checked={filters.email_verified}
onChange={(e) => updateFilter('email_verified', e.target.checked)}
/>
</Group>
{/* Filter Controls */}
{hasActiveFilters && (
<Group>
<Badge variant="light">
{Object.entries(filters).filter(([_, value]) =>
value !== undefined && value !== '' && value !== false
).length} filters active
</Badge>
<Button size="sm" onClick={() => {
clearFilters();
setPage(1);
}}>
Clear Filters
</Button>
</Group>
)}
</div>
{/* Users Table */}
<DataTable
records={users}
columns={[
{ accessor: 'name', sortable: true },
{ accessor: 'email', sortable: true },
{ accessor: 'role', sortable: true },
{ accessor: 'department', sortable: true },
{ accessor: 'created_at', sortable: true },
{ accessor: 'last_login', sortable: true }
]}
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
page={page}
onPageChange={setPage}
recordsPerPage={perPage}
onRecordsPerPageChange={setPerPage}
totalRecords={totalUsers}
/>
{/* Active Sort Indicator */}
{hasActiveSort && (
<Group mt="md">
<Badge>
Sorted by: {sortStatus?.columnAccessor} ({sortStatus?.direction})
</Badge>
<Button
size="xs"
variant="outline"
onClick={() => setSortStatus(null)}
>
Clear Sort
</Button>
</Group>
)}
</div>
);
}
Report Dashboard with Date Filtering
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
function SalesReport() {
const {
filter,
sort,
page,
perPage,
filters,
setFilters,
setPage,
setSortStatus,
updateFilter,
hasActiveFilters
} = useFilterSortHandler(
{
date_from: dayjs().startOf('month').format('YYYY-MM-DD'),
date_to: dayjs().format('YYYY-MM-DD'),
sales_rep_id: undefined,
region_id: undefined,
product_category: undefined,
min_amount: undefined,
max_amount: undefined
},
{ columnAccessor: 'created_at', direction: 'desc' },
{
formatDateValues: true,
encodeFilters: true,
onFiltersChange: (filters, processedFilter) => {
// Track filter changes for analytics
analytics.track('report_filters_changed', {
filters: filters,
processedFilter: processedFilter
});
}
}
);
// Real-time data fetching
const { data: reportData, isLoading } = useQuery(
['sales-report', filter, sort, page, perPage],
() => api.reports.sales({ filter, sort, page, perPage }),
{
refetchInterval: 30000, // Refresh every 30 seconds
keepPreviousData: true
}
);
return (
<div>
<h1>Sales Report</h1>
{/* Report Filters */}
<Card shadow="sm" mb="lg">
<DateRangePicker
label="Date Range"
value={[
filters.date_from ? new Date(filters.date_from) : null,
filters.date_to ? new Date(filters.date_to) : null
]}
onChange={(dates) => {
updateFilter('date_from', dates[0]);
updateFilter('date_to', dates[1]);
setPage(1); // Reset to first page
}}
/>
<Select
label="Sales Representative"
value={filters.sales_rep_id}
onChange={(value) => updateFilter('sales_rep_id', value)}
data={salesReps}
/>
<NumberInput
label="Minimum Amount"
value={filters.min_amount}
onChange={(value) => updateFilter('min_amount', value)}
min={0}
/>
<NumberInput
label="Maximum Amount"
value={filters.max_amount}
onChange={(value) => updateFilter('max_amount', value)}
min={0}
/>
</Card>
{/* Summary Cards */}
<Group mb="lg">
<Card>
<Text size="sm" color="dimmed">Total Sales</Text>
<Text size="xl" weight="bold">
${reportData?.summary.total_sales?.toLocaleString()}
</Text>
</Card>
<Card>
<Text size="sm" color="dimmed">Total Orders</Text>
<Text size="xl" weight="bold">
{reportData?.summary.total_orders?.toLocaleString()}
</Text>
</Card>
</Group>
{/* Data Table */}
<DataTable
records={reportData?.sales}
columns={[
{ accessor: 'order_id', sortable: true },
{ accessor: 'customer_name', sortable: true },
{ accessor: 'sales_rep', sortable: true },
{ accessor: 'amount', sortable: true },
{ accessor: 'created_at', sortable: true }
]}
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
page={page}
onPageChange={setPage}
recordsPerPage={perPage}
onRecordsPerPageChange={setPerPage}
totalRecords={reportData?.total}
loading={isLoading}
/>
</div>
);
}
🔧 Advanced Configuration
Custom Filter Processing
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
function CustomFilterHandler() {
const { filter, setFilters } = useFilterSortHandler(
{
search: '',
category: undefined,
tags: [], // Array filter
custom_field: undefined
},
null,
{
encodeFilters: true,
onFiltersChange: (filters, processedFilter) => {
// Custom processing before API call
const customProcessed = processSpecialFilters(filters);
// Call API with custom processed filters
api.getData({ filter: customProcessed });
}
}
);
const processSpecialFilters = (filters) => {
const processed = { ...filters };
// Handle array filters (tags)
if (filters.tags?.length > 0) {
processed.tags = filters.tags.join(','); // Convert array to comma-separated string
}
// Handle custom field transformation
if (filters.custom_field) {
processed.custom_field = transformCustomValue(filters.custom_field);
}
return processed;
};
return (
<div>
<TagsInput
value={filters.tags}
onChange={(tags) => setFilters({ tags })}
/>
</div>
);
}
Integration with React Query
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
import { useQuery, useMutation } from '@tanstack/react-query';
function QueryIntegratedComponent() {
const {
filter,
sort,
page,
perPage,
filters,
setFilters,
setPage,
setPerPage,
setSortStatus,
clearFilters
} = useFilterSortHandler(
{ search: '', status: undefined },
{ columnAccessor: 'created_at', direction: 'desc' },
{
autoRefetch: true,
onFiltersChange: (filters, processedFilter) => {
// Invalidate and refetch query when filters change
queryClient.invalidateQueries(['data']);
}
}
);
// Query with automatic refetch
const { data, isLoading, error } = useQuery(
['data', filter, sort, page, perPage],
() => fetchData({ filter, sort, page, perPage }),
{
keepPreviousData: true,
staleTime: 5000
}
);
// Mutation with filter reset
const createMutation = useMutation(createItem, {
onSuccess: () => {
// Clear filters and reset to first page after successful creation
clearFilters();
setPage(1);
queryClient.invalidateQueries(['data']);
}
});
return (
<div>
{/* Filters and controls */}
<input
value={filters.search}
onChange={(e) => setFilters({ search: e.target.value })}
/>
{/* Data display */}
{isLoading && <div>Loading...</div>}
{error && <div>Error: {error.message}</div>}
{data && <DataList items={data.items} />}
</div>
);
}
Debounced Filter Updates
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler';
import { useCallback } from 'react';
function DebouncedFilterComponent() {
const {
filter,
filters,
setFilters,
updateFilter,
hasActiveFilters
} = useFilterSortHandler(
{ search: '', category: undefined },
null,
{
onFiltersChange: useCallback(
debounce((filters, processedFilter) => {
// Debounced API call
api.fetchData({ filter: processedFilter });
}, 500),
[]
)
}
);
return (
<div>
<TextInput
placeholder="Search..."
value={filters.search}
onChange={(e) => updateFilter('search', e.target.value)}
/>
{/* Display will update immediately but API call is debounced */}
<div>Current filter: {filter}</div>
</div>
);
}
// Debounce utility
function debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
}
🎯 Filter Operators Support
Supported Operators
Hook ini support berbagai operator untuk advanced filtering:
// Equality operators
{ name: 'John' } // equals
{ name: { eq: 'John' } } // explicit equals
{ name: { ne: 'John' } } // not equals
// Comparison operators
{ age: { gt: 18 } } // greater than
{ age: { gte: 18 } } // greater than or equal
{ age: { lt: 65 } } // less than
{ age: { lte: 65 } } // less than or equal
// String operators
{ name: { contains: 'John' } } // contains substring
{ name: { startswith: 'J' } } // starts with
{ name: { endswith: 'n' } } // ends with
// Array operators
{ tags: { in: ['tag1', 'tag2'] } } // in array
{ tags: { nin: ['tag1', 'tag2'] } } // not in array
// Null operators
{ category: { isnull: true } } // is null
{ category: { notnull: true } } // is not null
// Boolean operators
{ active: true } // boolean true
{ archived: false } // boolean false
// Date operators (with date formatting)
{ created_at: { gt: '2024-01-01' } }
{ created_at: { lte: '2024-12-31' } }
Complex Filter Examples
function ComplexFilters() {
const { setFilters } = useFilterSortHandler(
{
// Text search with contains
search: { contains: 'john' },
// Date range
created_at: {
gte: '2024-01-01',
lte: '2024-12-31'
},
// Numeric range
price: {
gte: 100,
lte: 1000
},
// Multiple categories
category: { in: ['electronics', 'books'] },
// Exclude archived items
archived: { ne: true },
// Complex nested filters
or: [
{ featured: true },
{ rating: { gte: 4.5 } }
]
}
);
return <div>Complex filter example</div>;
}
🔧 Dependencies
Required Dependencies
npm install mantine-datatable dayjs
Internal Dependencies
@/utils/filter-sort-handler- Core filter and sort processing@/utils/filter-handler- Individual filter processing@/utils/sort-handler- Sort condition processing
Type Dependencies
import type { DataTableSortStatus } from 'mantine-datatable';
interface SortCondition {
key: string;
direction: 'asc' | 'desc';
}
🚨 Error Handling
Error Recovery
function ErrorHandlingFilters() {
const [error, setError] = useState(null);
const {
filter,
setFilters,
clearFilters
} = useFilterSortHandler(
{ search: '', category: undefined },
null,
{
onFiltersChange: async (filters, processedFilter) => {
try {
setError(null);
await fetchData({ filter: processedFilter });
} catch (err) {
setError(err.message);
// Optionally revert filters
// clearFilters();
}
}
}
);
return (
<div>
{error && (
<Alert color="red" title="Filter Error">
{error}
<Button size="sm" onClick={clearFilters}>
Reset Filters
</Button>
</Alert>
)}
{/* Filter inputs */}
</div>
);
}
Validation
function ValidatedFilters() {
const {
filter,
filters,
setFilters,
updateFilter
} = useFilterSortHandler(
{
email: '',
age: undefined,
date_range: undefined
},
null,
{
onFiltersChange: (filters, processedFilter) => {
// Validate filters before processing
if (filters.email && !isValidEmail(filters.email)) {
throw new Error('Invalid email format');
}
if (filters.age && (filters.age < 18 || filters.age > 100)) {
throw new Error('Age must be between 18 and 100');
}
// Process valid filters
fetchData({ filter: processedFilter });
}
}
);
const isValidEmail = (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
return (
<div>
<TextInput
label="Email"
value={filters.email}
onChange={(e) => updateFilter('email', e.target.value)}
error={filters.email && !isValidEmail(filters.email) ? 'Invalid email' : null}
/>
<NumberInput
label="Age"
value={filters.age}
onChange={(value) => updateFilter('age', value)}
min={18}
max={100}
/>
</div>
);
}
📚 Best Practices
1. Performance Optimization
// Use useCallback for filter change handlers
const handleFilterChange = useCallback((key, value) => {
updateFilter(key, value);
}, [updateFilter]);
// Debounce rapid filter changes
const debouncedUpdateFilter = useMemo(
() => debounce(updateFilter, 300),
[updateFilter]
);
2. Type Safety
// Define strict filter types
interface UserFilters {
search: string;
role?: 'admin' | 'user' | 'moderator';
department?: string;
created_after?: string;
is_active?: boolean;
}
function UserList() {
const { filters, setFilters } = useFilterSortHandler<UserFilters>(
{ search: '', role: undefined, department: undefined, created_after: undefined, is_active: undefined },
null,
{ formatDateValues: true }
);
// TypeScript will enforce correct types
setFilters({ role: 'admin' }); // Valid
// setFilters({ role: 'invalid' }); // TypeScript error
}
3. URL Synchronization
function URLSyncedFilters() {
const { filter, setFilters, setPage } = useFilterSortHandler(
getInitialFiltersFromURL(),
null,
{
onFiltersChange: (filters, processedFilter) => {
// Update URL without page reload
const url = new URL(window.location);
if (processedFilter) {
url.searchParams.set('filter', processedFilter);
} else {
url.searchParams.delete('filter');
}
url.searchParams.set('page', '1'); // Reset page
window.history.pushState({}, '', url);
}
}
);
// Listen to browser back/forward
useEffect(() => {
const handlePopState = () => {
const urlFilters = getFiltersFromURL();
setFilters(urlFilters);
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, [setFilters]);
return <div>URL-synced filters</div>;
}
🔍 Troubleshooting
Common Issues
Problem: Filters not updating UI
Solution: Ensure you're using the returned filters state, not the initial filters
Problem: Sort not working with DataTable
Solution: Use sortStatus and setSortStatus from the hook return values
Problem: Date filters not formatting correctly
Solution: Set formatDateValues: true in options
Problem: API calls not triggering
Solution: Implement onFiltersChange callback to handle API calls
Debug Mode
function DebugFilters() {
const {
filter,
sort,
filters,
sortStatus,
hasActiveFilters,
hasActiveSort
} = useFilterSortHandler(
{ search: '', category: undefined },
{ columnAccessor: 'name', direction: 'asc' },
{
onFiltersChange: (filters, processedFilter) => {
console.log('Raw filters:', filters);
console.log('Processed filter:', processedFilter);
},
onSortChange: (sort, processedSort) => {
console.log('Raw sort:', sort);
console.log('Processed sort:', processedSort);
}
}
);
return (
<div>
<div>Debug Info:</div>
<pre>Filter: {JSON.stringify(filter, null, 2)}</pre>
<pre>Sort: {JSON.stringify(sort, null, 2)}</pre>
<pre>Filters State: {JSON.stringify(filters, null, 2)}</pre>
<pre>Sort Status: {JSON.stringify(sortStatus, null, 2)}</pre>
<div>Has Active Filters: {hasActiveFilters.toString()}</div>
<div>Has Active Sort: {hasActiveSort.toString()}</div>
</div>
);
}
🔗 Related Hooks & Utilities
- filter-sort-handler - Core filter and sort processing utility
- sort-handler - Sort processing utility