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
- Yarn
npm install @haus-storefront-react/filters
yarn add @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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
enabledFilters | EnabledFilter[] | No | - | Which filters are enabled |
initialFilters | ActiveFilters | No | - | Initial filter state |
onFiltersChanged | (filters: ActiveFilters) => void | No | - | Callback when filters change |
onClearFilters | () => void | No | - | Callback when filters are cleared |
productListIdentifier | string | No | - | Identifier for the product list |
strategies | FiltersStrategies | No | - | Custom filter strategies |
children | ChildrenProps<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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
facetCode | string | Yes | - | Facet code to group by |
children | ChildrenProps<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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
facetCode | string | Yes | - | Facet code |
value | string | Yes | - | Value id |
asChild | boolean | No | false | If 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | ChildrenProps<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
| Parameter | Type | Required | Description |
|---|---|---|---|
options | FiltersVariables | Yes | Configuration object |
Options Object
interface FiltersVariables {
enabledFilters?: EnabledFilter[]
initialFilters?: ActiveFilters
onFiltersChanged?: (filters: ActiveFilters) => void
onClearFilters?: () => void
productListIdentifier?: string
strategies?: FiltersStrategies
}
Returns
| Return Value | Type | Description |
|---|---|---|
activeFilters | ActiveFilters | Current active filter state |
setActiveFilters | (draft: Draft<ActiveFilters> | ActiveFilters) => void | Function to set active filters |
enabledFilters | EnabledFilter[] | undefined | Enabled filters configuration |
initialFacetValues | FacetValueResult[] | undefined | Initial facet values from product list |
initialPriceValues | PriceRange | undefined | Initial price range from product list |
currencyCode | CurrencyCode | undefined | Currency code from product list |
handleActiveFilters | (value: string | undefined, key: string, add: boolean, update: boolean) => void | Internal handler for filter changes |
clearFilters | () => void | Function to clear all filters |
facetValues | FacetValueResult[] | undefined | Current facet values from product list |
hasActiveFilters | boolean | Whether any filters are active |
numActiveFilters | number | Total 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
| Parameter | Type | Required | Description |
|---|---|---|---|
options | UseFiltersPropsOptions | Yes | Configuration 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 Value | Type | Description |
|---|---|---|
filters | Filter[] | Array of filter objects for rendering |
toggleFacetValue | (facetCode: string, valueId: string) => void | Toggle a facet value |
setPriceFilter | (min: number, max: number) => void | Set price range filter |
getCheckboxProps | (props: { value: string; facetCode: string } & Record<string, unknown>) => CheckboxProps | Get props for checkbox input |
getGroupProps | (props: { facetCode: string } & Record<string, unknown>) => GroupProps | Get props for filter group |
getClearFiltersProps | () => ClearFiltersProps | Get props for clear filters button |
Basic Usage
Simple Component
- React
- React Native
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>
)
}
import { Text, TextInput, Pressable } from 'react-native'
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) => (
<>
<Text>{filter.label}</Text>
{filter.values.map((value) => (
<Pressable
key={value.id}
onPress={() => ctx.toggleFacetValue('brand', value.id)}
>
<Text>{value.checked ? '✓' : '☐'} {value.name}</Text>
</Pressable>
))}
</>
)}
</Filters.Group>
<Filters.Price>
{({ min, max, value, setPriceFilter, currencyCode }) => (
<>
<TextInput
value={String(value[0])}
onChangeText={(text) =>
setPriceFilter(Number(text) || 0, value[1])
}
keyboardType='numeric'
/>
<TextInput
value={String(value[1])}
onChangeText={(text) =>
setPriceFilter(value[0], Number(text) || 0)
}
keyboardType='numeric'
/>
<Text>
{value[0]} - {value[1]} {currencyCode}
</Text>
</>
)}
</Filters.Price>
</>
)}
</Filters.Root>
)
}
Basic Hook Usage
- React
- React Native
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>
)
}
import { Text, Pressable } from 'react-native'
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 <Text>No filters active</Text>
}
return (
<>
<Text>Active filters: {numActiveFilters}</Text>
<Pressable onPress={clearFilters}>
<Text>Clear All</Text>
</Pressable>
</>
)
}
Advanced Usage
Complex Component Configuration
- React
- React Native
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>
)
}
import {
Filters,
useFilters,
FiltersStrategies,
} from '@haus-storefront-react/filters'
import { FiltersStoreStrategy } from '@haus-storefront-react/strategies'
import { View, Text, TextInput, Pressable } from 'react-native'
function AdvancedFilterConfiguration() {
const strategies: FiltersStrategies = {
filtersStoreStrategy: {
getInitialFilters: () => {
// Get filters from AsyncStorage or another store
return {}
},
setActiveFilters: (filters) => {
// Sync filters to persistent storage
console.log('Persisting filters', filters)
},
},
}
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) => (
<View>
<Text>Active filters: {ctx.numActiveFilters}</Text>
{ctx.filters.map((filter) => {
if (filter.type === 'facet') {
return (
<Filters.Group
key={filter.facetCode}
facetCode={filter.facetCode}
>
{(facetFilter) => (
<View>
<Text>{facetFilter.label}</Text>
{facetFilter.values.map((value) => (
<Pressable
key={value.id}
onPress={() => ctx.toggleFacetValue(filter.facetCode, value.id)}
>
<View>
<Text>{value.checked ? '✓' : '☐'} {value.name}</Text>
</View>
</Pressable>
))}
</View>
)}
</Filters.Group>
)
}
return null
})}
<Filters.Price>
{({ min, max, value, setPriceFilter, currencyCode, label }) => (
<View>
<Text>{label}</Text>
<TextInput
value={String(value[0])}
onChangeText={(text) =>
setPriceFilter(Number(text) || 0, value[1])
}
keyboardType='numeric'
/>
<TextInput
value={String(value[1])}
onChangeText={(text) =>
setPriceFilter(value[0], Number(text) || 0)
}
keyboardType='numeric'
/>
<Text>{currencyCode}</Text>
</View>
)}
</Filters.Price>
{hasActiveFilters && (
<Pressable onPress={clearFilters}>
<Text>Clear All Filters</Text>
</Pressable>
)}
</View>
)}
</Filters.Root>
)
}
Conditional Rendering Patterns
- React
- React Native
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>
)
}
import { Filters } from '@haus-storefront-react/filters'
import { View, Text, TextInput, Pressable } from 'react-native'
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 <Text>No filters available</Text>
}
return (
<View>
<Text>Filters ({ctx.numActiveFilters} active)</Text>
{ctx.filters.map((filter) => {
if (filter.type === 'facet') {
return (
<Filters.Group
key={filter.facetCode}
facetCode={filter.facetCode}
>
{(facetFilter) =>
facetFilter.values.length > 0 ? (
<View>
<Text>{facetFilter.label}</Text>
{facetFilter.values.map((value) => (
<Pressable
key={value.id}
onPress={() => ctx.toggleFacetValue(filter.facetCode, value.id)}
>
<View>
<Text>{value.checked ? '✓' : '☐'} {value.name}</Text>
</View>
</Pressable>
))}
</View>
) : null
}
</Filters.Group>
)
}
if (filter.type === 'price') {
return (
<Filters.Price key='price'>
{({
min,
max,
value,
setPriceFilter,
currencyCode,
label,
}) =>
min !== max ? (
<View>
<Text>{label}</Text>
<TextInput
value={String(value[0])}
onChangeText={(text) =>
setPriceFilter(Number(text) || 0, value[1])
}
keyboardType='numeric'
/>
<TextInput
value={String(value[1])}
onChangeText={(text) =>
setPriceFilter(value[0], Number(text) || 0)
}
keyboardType='numeric'
/>
<Text>
{value[0]} - {value[1]} {currencyCode}
</Text>
</View>
) : null
}
</Filters.Price>
)
}
return null
})}
{ctx.hasActiveFilters && (
<Pressable onPress={ctx.clearFilters}>
<Text>Clear All Filters</Text>
</Pressable>
)}
</View>
)
}}
</Filters.Root>
)
}
Made with ❤️ by Haus Tech Team