Skip to main content

Filters

Headless product filtering components for e-commerce applications with facet-based filtering, price range filtering, and filter state management.

Purpose

This library provides comprehensive filtering functionality for product listings, including facet-based filtering, price range filtering, and sorting options. It's designed as a headless component that provides all the filtering logic while allowing complete UI customization.

Features

  • Facet-based filtering with checkbox selection
  • Price range filtering with min/max values
  • Filter state management and synchronization
  • Active filter tracking and display
  • Filter reset functionality
  • Event-based communication with product lists
  • Customizable filter strategies

Installation

npm install @haus-storefront-react/filters

Note: This is not a public package. Contact the Haus Tech Team for access.

API Reference

Filters.Root

Root component that provides filter context to all child components. Must wrap all filter components.

Props

PropTypeRequiredDefaultDescription
enabledFiltersEnabledFilter[]No-Which filters are enabled
initialFiltersActiveFiltersNo-Initial filter state
onFiltersChanged(filters: ActiveFilters) => voidNo-Callback when filters change
onClearFilters() => voidNo-Callback when filters are cleared
productListIdentifierstringNo-Identifier for the product list
strategiesFiltersStrategiesNo-Custom filter strategies
childrenChildrenProps<FiltersContextValue>No-Render prop that receives filter context

Filters.Group

Groups a facet filter. Must be used inside <Filters.Root>. Returns null if the facet is not found.

Props

PropTypeRequiredDefaultDescription
facetCodestringYes-Facet code to group by
childrenChildrenProps<FacetFilterData>No-Render prop that receives facet filter data

FacetFilterData Interface

interface FacetFilterData {
type: 'facet'
facetCode: string
label: string
values: Array<{
id: string
name: string
checked: boolean
}>
}

Filters.Checkbox

Checkbox component for a facet value. Must be used inside <Filters.Group>. Accepts all standard input checkbox props.

Props

PropTypeRequiredDefaultDescription
facetCodestringYes-Facet code
valuestringYes-Value id
asChildbooleanNofalseIf a custom component should be used
...Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'checked' | 'onChange'>No-All other input checkbox props

Filters.Price

Price filter component. Must be used inside <Filters.Root>. Returns null if price filter is not enabled.

Props

PropTypeRequiredDefaultDescription
childrenChildrenProps<PriceFilterData>No-Render prop that receives price range and functions

PriceFilterData Interface

interface PriceFilterData {
min: number
max: number
value: [number, number]
setPriceFilter: (min: number, max: number) => void
currencyCode?: string
label: string
}

Filters.createScope

Function to create a new Filters scope for advanced use cases where multiple filter contexts are needed.

Signature

function createScope(): Scope

useFilters

Hook for managing filter state and operations. Provides access to active filters, facet values, price ranges, and filter manipulation functions.

Parameters

ParameterTypeRequiredDescription
optionsFiltersVariablesYesConfiguration object

Options Object

interface FiltersVariables {
enabledFilters?: EnabledFilter[]
initialFilters?: ActiveFilters
onFiltersChanged?: (filters: ActiveFilters) => void
onClearFilters?: () => void
productListIdentifier?: string
strategies?: FiltersStrategies
}

Returns

Return ValueTypeDescription
activeFiltersActiveFiltersCurrent active filter state
setActiveFilters(draft: Draft<ActiveFilters> | ActiveFilters) => voidFunction to set active filters
enabledFiltersEnabledFilter[] | undefinedEnabled filters configuration
initialFacetValuesFacetValueResult[] | undefinedInitial facet values from product list
initialPriceValuesPriceRange | undefinedInitial price range from product list
currencyCodeCurrencyCode | undefinedCurrency code from product list
handleActiveFilters(value: string | undefined, key: string, add: boolean, update: boolean) => voidInternal handler for filter changes
clearFilters() => voidFunction to clear all filters
facetValuesFacetValueResult[] | undefinedCurrent facet values from product list
hasActiveFiltersbooleanWhether any filters are active
numActiveFiltersnumberTotal number of active filter values

useFiltersProps

Hook that extends useFilters with headless API helpers for building filter components. This is used internally by Filters.Root but is exported for advanced use cases.

Parameters

ParameterTypeRequiredDescription
optionsUseFiltersPropsOptionsYesConfiguration object

Options Object

interface UseFiltersPropsOptions {
enabledFilters?: EnabledFilter[]
initialFilters?: ActiveFilters
onFiltersChanged?: (filters: ActiveFilters) => void
onClearFilters?: () => void
productListIdentifier?: string
strategies?: FiltersStrategies
}

Returns

All values from useFilters plus:

Return ValueTypeDescription
filtersFilter[]Array of filter objects for rendering
toggleFacetValue(facetCode: string, valueId: string) => voidToggle a facet value
setPriceFilter(min: number, max: number) => voidSet price range filter
getCheckboxProps(props: { value: string; facetCode: string } & Record<string, unknown>) => CheckboxPropsGet props for checkbox input
getGroupProps(props: { facetCode: string } & Record<string, unknown>) => GroupPropsGet props for filter group
getClearFiltersProps() => ClearFiltersPropsGet props for clear filters button

Basic Usage

Simple Component

Filters

brand

Price Range

0
1000
0 -1000
import { Filters } from '@haus-storefront-react/filters'

function ProductFilters() {
const enabledFilters = [
{ type: 'facet', facetCode: 'brand' },
{ type: 'price' },
]

return (
<Filters.Root
enabledFilters={enabledFilters}
productListIdentifier='main-product-list'
>
{(ctx) => (
<>
<Filters.Group facetCode='brand'>
{(filter) => (
<div>
<h3>{filter.label}</h3>
{filter.values.map((value) => (
<label key={value.id}>
<Filters.Checkbox facetCode='brand' value={value.id} />
{value.name}
</label>
))}
</div>
)}
</Filters.Group>
<Filters.Price>
{({ min, max, value, setPriceFilter, currencyCode }) => (
<div>
<input
type='range'
min={min}
max={max}
value={value[0]}
onChange={(e) =>
setPriceFilter(Number(e.target.value), value[1])
}
/>
<input
type='range'
min={min}
max={max}
value={value[1]}
onChange={(e) =>
setPriceFilter(value[0], Number(e.target.value))
}
/>
<span>
{value[0]} - {value[1]} {currencyCode}
</span>
</div>
)}
</Filters.Price>
</>
)}
</Filters.Root>
)
}

Basic Hook Usage

import { useFilters } from '@haus-storefront-react/filters'

function FilterManager() {
const { activeFilters, clearFilters, hasActiveFilters, numActiveFilters } =
useFilters({
enabledFilters: [{ type: 'facet', facetCode: 'brand' }],
productListIdentifier: 'main-product-list',
})

if (!hasActiveFilters) {
return <div>No filters active</div>
}

return (
<div>
<p>Active filters: {numActiveFilters}</p>
<button onClick={clearFilters}>Clear All</button>
</div>
)
}

Advanced Usage

Complex Component Configuration

import {
Filters,
useFilters,
FiltersStrategies,
} from '@haus-storefront-react/filters'
import { FiltersStoreStrategy } from '@haus-storefront-react/strategies'

function AdvancedFilterConfiguration() {
const strategies: FiltersStrategies = {
filtersStoreStrategy: {
getInitialFilters: () => {
// Get filters from URL or storage
const params = new URLSearchParams(window.location.search)
const brandParam = params.get('brand')
return brandParam ? { brand: [brandParam] } : {}
},
setActiveFilters: (filters) => {
// Sync filters to URL or storage
const params = new URLSearchParams(window.location.search)
if (filters.brand && filters.brand.length > 0) {
params.set('brand', filters.brand[0])
} else {
params.delete('brand')
}
window.history.replaceState({}, '', `?${params.toString()}`)
},
},
}

const { activeFilters, clearFilters, hasActiveFilters } = useFilters({
enabledFilters: [
{ type: 'facet', facetCode: 'brand' },
{ type: 'facet', facetCode: 'category' },
{ type: 'price' },
],
productListIdentifier: 'main-product-list',
strategies,
onFiltersChanged: (filters) => {
console.log('Filters updated:', filters)
},
})

return (
<Filters.Root
enabledFilters={[
{ type: 'facet', facetCode: 'brand' },
{ type: 'facet', facetCode: 'category' },
{ type: 'price' },
]}
productListIdentifier='main-product-list'
strategies={strategies}
initialFilters={activeFilters}
>
{(ctx) => (
<div>
<div>Active filters: {ctx.numActiveFilters}</div>
{ctx.filters.map((filter) => {
if (filter.type === 'facet') {
return (
<Filters.Group
key={filter.facetCode}
facetCode={filter.facetCode}
>
{(facetFilter) => (
<fieldset>
<legend>{facetFilter.label}</legend>
{facetFilter.values.map((value) => (
<label key={value.id}>
<Filters.Checkbox
facetCode={filter.facetCode}
value={value.id}
/>
{value.name}
</label>
))}
</fieldset>
)}
</Filters.Group>
)
}
return null
})}
<Filters.Price>
{({ min, max, value, setPriceFilter, currencyCode, label }) => (
<fieldset>
<legend>{label}</legend>
<div>
<input
type='number'
min={min}
max={max}
value={value[0]}
onChange={(e) =>
setPriceFilter(Number(e.target.value), value[1])
}
/>
<span> - </span>
<input
type='number'
min={min}
max={max}
value={value[1]}
onChange={(e) =>
setPriceFilter(value[0], Number(e.target.value))
}
/>
<span> {currencyCode}</span>
</div>
</fieldset>
)}
</Filters.Price>
{hasActiveFilters && (
<button onClick={clearFilters}>Clear All Filters</button>
)}
</div>
)}
</Filters.Root>
)
}

Conditional Rendering Patterns

import { Filters } from '@haus-storefront-react/filters'

function ConditionalFilterComponent() {
const enabledFilters = [
{ type: 'facet', facetCode: 'brand' },
{ type: 'facet', facetCode: 'category' },
{ type: 'price' },
]

return (
<Filters.Root
enabledFilters={enabledFilters}
productListIdentifier='main-product-list'
>
{(ctx) => {
if (!ctx.filters || ctx.filters.length === 0) {
return <div>No filters available</div>
}

return (
<div>
<div>Filters ({ctx.numActiveFilters} active)</div>

{ctx.filters.map((filter) => {
if (filter.type === 'facet') {
return (
<Filters.Group
key={filter.facetCode}
facetCode={filter.facetCode}
>
{(facetFilter) =>
facetFilter.values.length > 0 ? (
<div>
<h3>{facetFilter.label}</h3>
{facetFilter.values.map((value) => (
<label key={value.id}>
<Filters.Checkbox
facetCode={filter.facetCode}
value={value.id}
/>
{value.name}
</label>
))}
</div>
) : null
}
</Filters.Group>
)
}

if (filter.type === 'price') {
return (
<Filters.Price key='price'>
{({
min,
max,
value,
setPriceFilter,
currencyCode,
label,
}) =>
min !== max ? (
<div>
<h3>{label}</h3>
<input
type='range'
min={min}
max={max}
value={value[0]}
onChange={(e) =>
setPriceFilter(Number(e.target.value), value[1])
}
/>
<input
type='range'
min={min}
max={max}
value={value[1]}
onChange={(e) =>
setPriceFilter(value[0], Number(e.target.value))
}
/>
<span>
{value[0]} - {value[1]} {currencyCode}
</span>
</div>
) : null
}
</Filters.Price>
)
}

return null
})}

{ctx.hasActiveFilters && (
<button onClick={ctx.clearFilters}>Clear All Filters</button>
)}
</div>
)
}}
</Filters.Root>
)
}

Made with ❤️ by Haus Tech Team