Skip to main content

useAdminFilters

Overview

useAdminFilters is a generic React hook that provides unified filter state management for admin pages. It combines debounced search (via useDebounceSearch), a status filter, and multiple multi-select filters into a single composable unit. The hook is designed to be consumed by domain-specific hooks like useAdminUsers and useAdminItems, but can also be used directly in components.

Source: template/hooks/use-admin-filters.ts

Signature / Parameters

function useAdminFilters<TStatus extends string = string>(
config?: AdminFiltersConfig<TStatus>
): UseAdminFiltersReturn<TStatus>

The TStatus generic parameter constrains the allowed values for the status filter (e.g., 'active' | 'inactive' or 'draft' | 'pending' | 'approved' | 'rejected').

AdminFiltersConfig<TStatus>

ParameterTypeDefaultDescription
minSearchLengthnumber2Minimum character count before search triggers debounce
debounceDelaynumber300Debounce delay in milliseconds
initialStatusTStatus | ''''Initial status filter value
initialMultiFiltersRecord<string, string[]>{}Initial multi-select filter values keyed by filter name
onFiltersChange() => voidundefinedCallback fired when any filter changes (useful for page reset). Does not need to be memoized.

Return Values

Search State

PropertyTypeDescription
searchTermstringCurrent raw search input value
setSearchTerm(value: string) => voidUpdate the search input
debouncedSearchTermstringDebounced search value (only set when >= minSearchLength)
isSearchingbooleantrue when debounce is pending
hasActiveSearchbooleantrue when trimmed search meets minimum length
clearSearch() => voidReset the search term to empty string

Status Filter

PropertyTypeDescription
statusFilterTStatus | ''Current status filter value
setStatusFilter(status: TStatus | '') => voidUpdate the status filter

Multi-Select Filters

PropertyTypeDescription
multiFiltersRecord<string, string[]>Current multi-select filter values by filter name
setMultiFilter(filterName: string, values: string[]) => voidSet all values for a named filter
toggleMultiFilterValue(filterName: string, value: string) => voidToggle a single value within a named filter
clearMultiFilter(filterName: string) => voidClear all values for a named filter (sets to [])

Active Filter Utilities

PropertyTypeDescription
activeFilterCountnumberTotal number of active filters (search + status + multi-filter values)
hasActiveFiltersbooleantrue when any filter is active
clearAllFilters() => voidReset all filters to their default empty state
calculateActiveFilterCount() => numberUtility function to manually calculate filter count

Implementation Details

  • Debounced search: Uses the companion useDebounceSearch hook internally. The raw searchTerm is trimmed and only debounced if it meets the minSearchLength threshold. If below the threshold, the debounced value is set to an empty string.
  • Ref-based callback: The onFiltersChange callback is stored in a useRef to avoid dependency issues. This means consumers do not need to memoize the callback.
  • Initial mount guard: A useRef boolean (isInitialMount) prevents onFiltersChange from firing on the initial render. This avoids unnecessary page resets when filters initialize.
  • Filter change detection: A useEffect watches statusFilter and multiFilters and calls onFiltersChange on change (after initial mount). The debounced search triggers onFiltersChange via the useDebounceSearch onSearch callback.
  • Clear all: The clearAllFilters function resets search, status, and multi-filters, then immediately calls onFiltersChange (bypassing debounce) for consistent page reset behavior.
  • Active filter counting: Counts search (if active) as 1, status (if set) as 1, and each individual multi-filter value as 1. For example, if a user has an active search, a status filter, and 3 selected categories, activeFilterCount would be 5.

Usage Examples

Direct usage in a component

import { useAdminFilters } from '@/hooks/use-admin-filters';

type ItemStatus = 'draft' | 'pending' | 'approved' | 'rejected';

function ItemsFilterBar({ onPageReset }: { onPageReset: () => void }) {
const {
searchTerm,
setSearchTerm,
debouncedSearchTerm,
isSearching,
statusFilter,
setStatusFilter,
multiFilters,
setMultiFilter,
toggleMultiFilterValue,
activeFilterCount,
hasActiveFilters,
clearAllFilters,
} = useAdminFilters<ItemStatus>({
minSearchLength: 2,
debounceDelay: 300,
initialMultiFilters: { categories: [], tags: [] },
onFiltersChange: onPageReset,
});

return (
<div>
<SearchInput
value={searchTerm}
onChange={setSearchTerm}
isLoading={isSearching}
/>

<StatusSelect
value={statusFilter}
onChange={setStatusFilter}
options={['draft', 'pending', 'approved', 'rejected']}
/>

<CategoryMultiSelect
selected={multiFilters.categories || []}
onChange={(values) => setMultiFilter('categories', values)}
/>

<TagMultiSelect
selected={multiFilters.tags || []}
onToggle={(tag) => toggleMultiFilterValue('tags', tag)}
/>

{hasActiveFilters && (
<Button onClick={clearAllFilters}>
Clear All ({activeFilterCount})
</Button>
)}
</div>
);
}

Used inside a domain hook (as in useAdminUsers)

const {
searchTerm,
setSearchTerm,
debouncedSearchTerm,
isSearching,
hasActiveSearch,
statusFilter,
setStatusFilter,
multiFilters,
setMultiFilter,
activeFilterCount,
hasActiveFilters,
clearAllFilters,
} = useAdminFilters<'active' | 'inactive'>({
minSearchLength: 2,
debounceDelay: 300,
initialStatus: '',
initialMultiFilters: { role: [] },
});

// Derive a single-select role filter from multi-filters
const roleFilter = multiFilters.role?.[0] || '';
const setRoleFilter = (role: string) => setMultiFilter('role', role ? [role] : []);

Passing debounced values to a query

const { debouncedSearchTerm, statusFilter, multiFilters } = useAdminFilters({
onFiltersChange: () => setPage(1),
});

const { data } = useQuery({
queryKey: ['items', { search: debouncedSearchTerm, status: statusFilter, categories: multiFilters.categories }],
queryFn: () => fetchItems({
search: debouncedSearchTerm || undefined,
status: statusFilter || undefined,
categories: multiFilters.categories,
}),
});
  • useAdminUsers -- Consumes useAdminFilters internally for user search, role, and status filtering.
  • useAdminItems -- Item management with category and tag filtering.
  • useAdminReports -- Report filtering by status, content type, and reason.
  • useAdminClients -- Client filtering by status, plan, provider, and date ranges.