@haus-storefront-react/search
v0.0.50
Published
A headless, flexible search component for e-commerce storefronts. Supports real-time search, product and collection results, price display, and more. Built with TypeScript, accessibility-first design, and platform-agnostic architecture.
Downloads
485
Readme
Search Component
A headless, flexible search component for e-commerce storefronts. Supports real-time search, product and collection results, price display, and more. Built with TypeScript, accessibility-first design, and platform-agnostic architecture.
Installation
npm install @haus-storefront-react/search
# or
yarn add @haus-storefront-react/searchUsage Example
import { Price } from '@haus-storefront-react/common-ui'
import { Search } from '@haus-storefront-react/search'
import { Link } from '@tanstack/react-router'
;<Search.Root term="" take={5}>
{(searchContext) => (
<div>
<Search.Input placeholder="Search for products..." />
<Search.Clear>✕</Search.Clear>
<Search.Loading>
<div>Loading search results...</div>
</Search.Loading>
<Search.Empty>
<div>No results found. Try a different search term.</div>
</Search.Empty>
<Search.Results>
{({ isLoading, collections, items }) => {
if (isLoading) return <div>Loading...</div>
if (!items || items.length === 0) return <div>No results found</div>
return (
<div>
{/* Collections */}
{collections?.map((collection) => (
<Search.CollectionItem key={collection.id} collection={collection}>
<Search.CollectionImage alt={collection.name} />
<div>{collection.name}</div>
</Search.CollectionItem>
))}
{/* Products */}
{items?.map((product) => (
<Search.ProductItem key={product.productId} product={product}>
<Link to="/product/$productId" params={{ productId: product.productId }}>
<Search.ProductImage alt={product.productName} />
<div>{product.productName}</div>
<Search.Price>
{({ price, priceWithTax, currencyCode }) => (
<Price.Root
price={price}
priceWithTax={priceWithTax}
currencyCode={currencyCode}
asChild
>
<div>
<Price.Amount withCurrency />
<Price.Currency />
</div>
</Price.Root>
)}
</Search.Price>
</Link>
</Search.ProductItem>
))}
</div>
)
}}
</Search.Results>
</div>
)}
</Search.Root>Features
- 🔍 Real-time search with debounced API calls
- 🛒 Product and collection search with unified results
- ♿ Accessibility-first, platform-agnostic
- 💸 Price display with currency support
- 🖼️ Image handling for products and collections
- 🎨 Headless, fully customizable
- ⚡ TypeScript support
- 🔄 Loading and error states built-in
API Reference
<Search.Root>
Context provider for search functionality.
Props:
term: string– Initial search termtake?: number– Number of results to fetch (default: 3)groupByProduct?: boolean– Group results by product (default: true)children: (context) => ReactNode– Render prop with search context
<Search.Input>
Search input field. Accepts all <input> props
<Search.Clear>
Clear button that appears when there's a search term. Accepts all <button> props.
<Search.Results>
Container for search results. Render prop receives:
isLoading: boolean– Loading statecollections?: Collection[]– Collection resultsitems?: SearchResult[]– Product resultstotalItems?: number– Total number of products matching inputfacets?: Facet[]– Facet data
<Search.ProductItem>
Wraps a single product. Must be used inside Search.Results.
product: SearchResult– The product object
<Search.ProductImage>
Displays the product image. Accepts all <img> props.
<Search.CollectionItem>
Wraps a single collection. Must be used inside Search.Results.
collection: Collection– The collection object
<Search.CollectionImage>
Displays the collection image. Accepts all <img> props.
<Search.Price>
Render prop for price info:
price,priceWithTax,currencyCode,isFromPrice
<Search.Loading>
Component that shows during loading states. Accepts all <div> props.
<Search.Empty>
Component that shows when no results are found. Accepts all <div> props.
Search Context
The search context provides the following:
interface SearchContextValue {
data: UseSearchFieldResponse | null
isLoading: boolean
error: Error | null
variables: SearchVariables
setVariables: (variables: SearchVariables) => void
search: (term: string) => void
clear: () => void
}Integration with ProductList
For full search results with infinite pagination, use the ProductList component:
import { ProductList } from '@haus-storefront-react/product-list'
function SearchResultsPage() {
const [searchParams] = useSearchParams()
const searchTerm = searchParams.get('term') || ''
return (
<ProductList.Root
searchInputProps={{
term: searchTerm,
take: 20,
groupByProduct: true,
}}
infinitePagination={true}
>
{({ products, isLoading, error, totalItems }) => {
if (isLoading) return <div>Loading products...</div>
if (error) return <div>Error loading products</div>
if (!products.length) return <div>No products found</div>
return (
<>
<div>
Found {totalItems} products for "{searchTerm}"
</div>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
}}
>
{products.map((product) => (
<ProductList.Item key={product.productId} product={product}>
<ProductList.Image alt={product.productName} />
<h3>{product.productName}</h3>
<ProductList.Price>
{({ price, currencyCode }) => (
<span>
{price} {currencyCode}
</span>
)}
</ProductList.Price>
</ProductList.Item>
))}
</div>
<ProductList.Pagination.Root>
{({ canGoForward, nextPage }) => (
<button onClick={nextPage} disabled={!canGoForward}>
Load More Products
</button>
)}
</ProductList.Pagination.Root>
</>
)
}}
</ProductList.Root>
)
}Navigation Integration
For search with navigation to full results page:
import { Search } from '@haus-storefront-react/search'
import { useNavigate } from '@tanstack/react-router'
function SearchWithNavigation() {
const navigate = useNavigate()
return (
<Search.Root term="" take={5}>
{(searchContext) => (
<div>
<Search.Input
placeholder="Search for products..."
onKeyUp={(e) => {
if (e.key === 'Enter') {
const searchTerm = e.currentTarget.value
searchContext.search(searchTerm)
navigate({ to: '/search', search: { term: searchTerm } })
}
}}
/>
<Search.Results>
{({ isLoading, collections, items }) => (
<div>
{/* Show first collection only */}
{collections?.[0] && (
<Search.CollectionItem collection={collections[0]}>
<Search.CollectionImage alt={collections[0].name} />
<h4>{collections[0].name}</h4>
</Search.CollectionItem>
)}
{/* Products with navigation */}
{items?.map((product) => (
<Search.ProductItem key={product.productId} product={product}>
<Link to="/product/$productId" params={{ productId: product.productId }}>
<Search.ProductImage alt={product.productName} />
<h4>{product.productName}</h4>
<Search.Price>
{({ price, currencyCode }) => (
<span>
{price} {currencyCode}
</span>
)}
</Search.Price>
</Link>
</Search.ProductItem>
))}
{/* Show All Results Button */}
<button
onClick={() => {
const searchTerm = searchContext.variables.term
if (searchTerm) {
navigate({ to: '/search', search: { term: searchTerm } })
}
}}
>
Show All Results
</button>
</div>
)}
</Search.Results>
</div>
)}
</Search.Root>
)
}Error Handling
The search component handles errors gracefully:
- Network errors are caught and displayed
- Invalid search terms are handled
- Loading states prevent multiple simultaneous requests
- Empty states provide clear feedback
Integration
This component integrates seamlessly with other components in the ecosystem:
- ProductList – For full search results with pagination
- Price – For consistent price display
- Navigation – For routing to product and search pages
- Cart components – For add-to-cart functionality
See the source code and TypeScript types for full details and advanced usage.
