Strategies
URL-based state management strategies for filters, pagination, and product variant selection in e-commerce applications.
Purpose
This library provides configurable strategies for managing URL state synchronization with filters, pagination, and product variant selection in e-commerce applications. It enables deep linking, bookmarkable URLs, and browser history management for product listings and search results. The Strategy pattern implementation allows you to customize how filter and pagination state is stored and retrieved, with default URL-based implementations provided out of the box.
Features
- URL Synchronization: Automatic sync between component state and URL parameters
- Deep Linking: Shareable URLs that preserve filter, pagination, and product variant selection state
- Browser History: Proper back/forward navigation support using
replaceState - SSR Support: Graceful handling of server-side rendering (no-op when
windowis undefined) - Customizable: Implement custom strategies by implementing the strategy interfaces
- Platform Agnostic: Works on web, mobile, and SSR environments
- Performance Optimized: Minimal overhead with efficient URL updates
Installation
- npm
- Yarn
npm install @haus-storefront-react/strategies
yarn add @haus-storefront-react/strategies
Note: This is not a public package. Contact the Haus Tech Team for access.
API Reference
DefaultFilterUrlStrategy
Manages filter state in URL parameters using comma-separated values. Implements the FiltersStoreStrategy interface to synchronize filter state with URL query parameters. Returns empty object when window is undefined (SSR environments).
Methods
| Method | Signature | Description |
|---|---|---|
getInitialFilters | () => ActiveFilters | undefined | Retrieves active filters from URL query parameters. Parses comma-separated values from URL params into an ActiveFilters object. Returns empty object {} in SSR environments. |
setActiveFilters | (filters: ActiveFilters) => void | Updates URL query parameters with active filters. Converts filter arrays to comma-separated values. Filters out empty values and updates browser history using replaceState. No-op in SSR environments. |
DefaultPaginationUrlStrategy
Manages pagination state in URL parameters using a page query parameter. Implements the PaginationStoreStrategy interface to synchronize current page with URL. Returns 1 when window is undefined or when no page parameter exists.
Methods
| Method | Signature | Description |
|---|---|---|
getInitialPage | () => number | Retrieves the current page number from URL query parameters. Parses the page parameter from URL. Returns 1 if parameter is missing, invalid, or in SSR environments. |
setPage | (page: number) => void | Updates URL query parameters with the current page number. Sets page parameter when page > 1, removes parameter when page === 1. Updates browser history using replaceState. No-op in SSR environments. |
ProductVariantStoreStrategy / DefaultProductVariantUrlStrategy
ProductVariantStoreStrategy is the storage-agnostic interface for reading and persisting selected variant options (option group code → option ID). Implementations can sync to URL, Redux, or any other store. DefaultProductVariantUrlStrategy is the URL-based implementation: it uses one query parameter per option group (param name = option group code, value = option ID), e.g. ?size=opt-1&color=opt-2. Returns an empty object when window is undefined (SSR). Invalid or unknown option IDs are ignored by the consumer (product variant selector).
When used with the product variant selector, the URL is only updated after the user has actively changed a variant (e.g. by clicking an option). The initial or default selection on first page load is not written to the URL; once the user has interacted, subsequent changes (including switching back to the default variant) are synced.
Methods
| Method | Signature | Description |
|---|---|---|
getInitialOptions | () => SelectedVariantOptions | Returns the current selected options from the store. Consumer uses only keys that match product option group codes. |
setOptions | (options: SelectedVariantOptions, optionGroupCodes: string[]) => void | Persists the selected options. Clears existing values for optionGroupCodes first, then sets the new values. For the URL implementation: preserves pathname, other params, and hash. No-op in SSR. |
Basic Usage
Simple Filter Management
- React
- React Native
import React, { useState, useEffect } from 'react'
import { DefaultFilterUrlStrategy } from '@haus-storefront-react/strategies'
import { ActiveFilters } from '@haus-storefront-react/shared-types'
function ProductFilters() {
const [filters, setFilters] = useState<ActiveFilters>({})
const filterStrategy = new DefaultFilterUrlStrategy()
useEffect(() => {
// Initialize filters from URL
const initialFilters = filterStrategy.getInitialFilters()
setFilters(initialFilters || {})
}, [])
const updateFilters = (newFilters: ActiveFilters) => {
setFilters(newFilters)
filterStrategy.setActiveFilters(newFilters)
}
const handleBrandChange = (brands: string[]) => {
updateFilters({
...filters,
brand: brands,
})
}
return (
<div className='filters'>
<h3>Filters</h3>
<div className='filter-group'>
<label>Brand</label>
<select
multiple
value={filters.brand || []}
onChange={(e) => {
const selected = Array.from(
e.target.selectedOptions,
(option) => option.value,
)
handleBrandChange(selected)
}}
>
<option value='apple'>Apple</option>
<option value='samsung'>Samsung</option>
<option value='google'>Google</option>
</select>
</div>
</div>
)
}
import React, { useState, useEffect } from 'react'
import { View, Text, Button } from 'react-native'
import { DefaultFilterUrlStrategy } from '@haus-storefront-react/strategies'
import { ActiveFilters } from '@haus-storefront-react/shared-types'
function ProductFilters() {
const [filters, setFilters] = useState<ActiveFilters>({})
const filterStrategy = new DefaultFilterUrlStrategy()
useEffect(() => {
const initialFilters = filterStrategy.getInitialFilters()
setFilters(initialFilters || {})
}, [])
const updateFilters = (newFilters: ActiveFilters) => {
setFilters(newFilters)
filterStrategy.setActiveFilters(newFilters)
}
const handleBrandChange = (brands: string[]) => {
updateFilters({
...filters,
brand: brands,
})
}
return (
<>
<Text>Filters</Text>
<Button
title='Select Apple'
onPress={() => handleBrandChange(['apple'])}
/>
<Button
title='Select Samsung'
onPress={() => handleBrandChange(['samsung'])}
/>
</>
)
}
Simple Pagination Management
- React
- React Native
import React, { useState, useEffect } from 'react'
import { DefaultPaginationUrlStrategy } from '@haus-storefront-react/strategies'
function ProductPagination({ totalPages }: { totalPages: number }) {
const [currentPage, setCurrentPage] = useState(1)
const paginationStrategy = new DefaultPaginationUrlStrategy()
useEffect(() => {
// Initialize page from URL
const initialPage = paginationStrategy.getInitialPage()
setCurrentPage(initialPage)
}, [])
const handlePageChange = (page: number) => {
setCurrentPage(page)
paginationStrategy.setPage(page)
}
return (
<div className='pagination'>
<button
disabled={currentPage <= 1}
onClick={() => handlePageChange(currentPage - 1)}
>
Previous
</button>
<span>
Page {currentPage} of {totalPages}
</span>
<button
disabled={currentPage >= totalPages}
onClick={() => handlePageChange(currentPage + 1)}
>
Next
</button>
</div>
)
}
import React, { useState, useEffect } from 'react'
import { View, Text, Pressable } from 'react-native'
import { DefaultPaginationUrlStrategy } from '@haus-storefront-react/strategies'
function ProductPagination({ totalPages }: { totalPages: number }) {
const [currentPage, setCurrentPage] = useState(1)
const paginationStrategy = new DefaultPaginationUrlStrategy()
useEffect(() => {
const initialPage = paginationStrategy.getInitialPage()
setCurrentPage(initialPage)
}, [])
const handlePageChange = (page: number) => {
setCurrentPage(page)
paginationStrategy.setPage(page)
}
return (
<View>
<Pressable
onPress={() => handlePageChange(currentPage - 1)}
disabled={currentPage <= 1}
>
<Text>Previous</Text>
</Pressable>
<Text>
Page {currentPage} of {totalPages}
</Text>
<Pressable
onPress={() => handlePageChange(currentPage + 1)}
disabled={currentPage >= totalPages}
>
<Text>Next</Text>
</Pressable>
</View>
)
}
Advanced Usage
Extending Default Strategies
- React
- React Native
import { DefaultFilterUrlStrategy } from '@haus-storefront-react/strategies'
import { ActiveFilters } from '@haus-storefront-react/shared-types'
class AdvancedFilterStrategy extends DefaultFilterUrlStrategy {
setActiveFilters = (filters: ActiveFilters): void => {
// Call parent implementation
super.setActiveFilters(filters)
// Additional custom logic
this.trackFilterChanges(filters)
this.updatePageTitle(filters)
}
private trackFilterChanges = (filters: ActiveFilters): void => {
// Track filter changes for analytics
const filterCount = Object.values(filters).reduce(
(total, arr) => total + (arr?.length || 0),
0,
)
if (typeof gtag !== 'undefined') {
gtag('event', 'filter_applied', {
filter_count: filterCount,
filters: Object.keys(filters),
})
}
}
private updatePageTitle = (filters: ActiveFilters): void => {
// Update page title based on active filters
const filterNames = Object.keys(filters)
if (filterNames.length > 0) {
document.title = `Products - Filtered by ${filterNames.join(', ')}`
} else {
document.title = 'Products'
}
}
}
import { DefaultFilterUrlStrategy } from '@haus-storefront-react/strategies'
import { ActiveFilters } from '@haus-storefront-react/shared-types'
class AdvancedFilterStrategy extends DefaultFilterUrlStrategy {
setActiveFilters = (filters: ActiveFilters): void => {
super.setActiveFilters(filters)
this.trackFilterChanges(filters)
this.logTitle(filters)
}
private trackFilterChanges = (filters: ActiveFilters): void => {
const filterCount = Object.values(filters).reduce(
(total, arr) => total + (arr?.length || 0),
0,
)
console.log('Applied filters:', filterCount)
}
private logTitle = (filters: ActiveFilters): void => {
const filterNames = Object.keys(filters)
const title =
filterNames.length > 0
? `Products - Filtered by ${filterNames.join(', ')}`
: 'Products'
console.log(title)
}
}
Made with ❤️ by Haus Tech Team