Skip to main content

Search

A headless, flexible search component for e-commerce storefronts. Supports real-time search, product and collection results, price display, and more.

Purpose

This component provides a complete search solution for e-commerce applications. It handles real-time search with debouncing, displays product and collection results, supports price rendering, and integrates seamlessly with navigation systems. Use this component when you need search functionality that works across web, mobile, and other platforms with consistent behavior and API.

Features

  • Real-time search with debouncing (300ms delay)
  • Autocomplete suggestions for products and collections
  • Product search result display with images and pricing
  • Collection search result display
  • Loading and error states
  • Empty state handling
  • Accessibility-first design patterns

Installation

npm install @haus-storefront-react/search

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

API Reference

Search.Root

Root component for Search. Provides context to all child components. Must wrap all other Search components.

Props

PropTypeRequiredDefaultDescription
termstring | undefinedNo-Initial search term
takenumber | undefinedNo3Number of results to fetch
groupByProductboolean | undefinedNotrueGroup results by product
collectionIdstring | undefinedNo-Filter by collection ID
collectionSlugstring | undefinedNo-Filter by collection slug
facetValueIdsArray<string> | undefinedNo-Filter by facet value IDs
facetValueOperatorLogicalOperator | undefinedNo-Operator for facet value filters
facetValueFiltersArray<FacetValueFilterInput> | undefinedNo-Facet value filter inputs
inStockboolean | undefinedNo-Filter by in-stock status
sortSearchResultSortParameter | undefinedNo-Sort parameters for results
skipnumber | undefinedNo-Number of results to skip
children(context: UseSearchReturn) => ReactNodeNo-Render prop with search context

Search.Input

Input component for the search field. Must be used within Search.Root. Automatically debounces input and triggers search.

Props

Accepts all standard <input> props plus:

PropTypeRequiredDefaultDescription
asChildbooleanNofalseRender as child component
__scopeSearchScopeNo-Internal scope prop

Search.Clear

Clear button for the search input. Must be used within Search.Root. Only renders when there is a search term.

Props

Accepts all standard <button> props plus:

PropTypeRequiredDefaultDescription
asChildbooleanNofalseRender as child component
__scopeSearchScopeNo-Internal scope prop

Search.Results

Results container for search results. Must be used within Search.Root. Provides render prop with search data.

Props

PropTypeRequiredDefaultDescription
asChildbooleanNofalseRender as child component
children(data: Partial<UseSearchFieldResponse> & { isLoading: boolean; error: Error | null }) => ReactNodeYes-Render prop that receives search data
__scopeSearchScopeNo-Internal scope prop

Accepts all standard <div> props.

Render Prop Data

PropertyTypeDescription
collectionsCollectionSearchResponse[] | undefinedCollection results
itemsSearchResult[] | undefinedProduct results
totalItemsnumber | undefinedTotal number of products matching input
facetsGroupedFacetValues | undefinedFacet data
isLoadingbooleanLoading state flag
errorError | nullError object if request failed

Search.ProductItem

Item component for a single product. Must be used within Search.Results.

Props

PropTypeRequiredDefaultDescription
productSearchResultYes-Product data to display
childrenReactNodeNo-Child components
__scopeSearchScopeNo-Internal scope prop

Search.ProductImage

Image component for displaying the product image. Must be used within Search.ProductItem. Returns null if no image is available.

Props

Accepts all standard <img> props plus:

PropTypeRequiredDefaultDescription
altstringNoproduct.productNameAlt text for the image
asChildbooleanNofalseRender as child component
__scopeSearchScopeNo-Internal scope prop

Search.CollectionItem

Item component for a single collection. Must be used within Search.Results.

Props

PropTypeRequiredDefaultDescription
collectionCollectionYes-Collection data to display
childrenReactNodeNo-Child components
__scopeSearchScopeNo-Internal scope prop

Search.CollectionImage

Image component for displaying the collection image. Must be used within Search.CollectionItem. Returns null if no image is available.

Props

Accepts all standard <img> props plus:

PropTypeRequiredDefaultDescription
altstringNocollection.nameAlt text for the image
srcstringNocollection.featuredAsset?.previewImage source URL
asChildbooleanNofalseRender as child component
__scopeSearchScopeNo-Internal scope prop

Search.Price

Price component for a product. Must be used within Search.ProductItem. Provides render prop with price data.

Props

PropTypeRequiredDefaultDescription
children(props: { price: Price; priceWithTax: Price; currencyCode: CurrencyCode; isFromPrice: boolean }) => ReactNodeNo-Render prop that receives price data
__scopeSearchScopeNo-Internal scope prop

Render Prop Data

PropertyTypeDescription
pricePriceProduct price (may be range, single value, or number)
priceWithTaxPriceProduct price with tax
currencyCodeCurrencyCodeCurrency code for the price
isFromPricebooleanWhether this is a price range (from X to Y)

Search.Loading

Loading component for search loading state. Must be used within Search.Root. Only renders when search is in loading state.

Props

Accepts all standard <div> props plus:

PropTypeRequiredDefaultDescription
asChildbooleanNofalseRender as child component
childrenReactNodeNo-Content to display while loading
__scopeSearchScopeNo-Internal scope prop

Search.Empty

Empty state component for when no results are found. Must be used within Search.Root. Only renders when search is complete, has a search term, and no results are found.

Props

Accepts all standard <div> props plus:

PropTypeRequiredDefaultDescription
asChildbooleanNofalseRender as child component
childrenReactNodeNo-Content to display when empty
__scopeSearchScopeNo-Internal scope prop

useSearch

Hook for managing search state and performing search operations. Handles debouncing, loading states, and error handling.

Parameters

ParameterTypeRequiredDescription
initialVariablesSearchInputYesInitial search variables including term, take, groupByProduct, etc.

Options Object

interface SearchInput {
term?: Maybe<string>
collectionId?: Maybe<string>
collectionSlug?: Maybe<string>
facetValueIds?: Maybe<Array<string>>
facetValueOperator?: Maybe<LogicalOperator>
facetValueFilters?: Maybe<Array<FacetValueFilterInput>>
groupByProduct?: Maybe<boolean>
inStock?: Maybe<boolean>
sort?: Maybe<SearchResultSortParameter>
take?: Maybe<number>
skip?: Maybe<number>
}

Returns

Return ValueTypeDescription
dataUseSearchFieldResponse | nullThe loaded search data or null while loading/error
isLoadingbooleanLoading state flag
errorError | nullError object if request failed
variablesSearchInputCurrent search variables
setVariables(variables: SearchInput) => voidFunction to update search variables
search(term: string) => voidFunction to update search term and trigger search
clear() => voidFunction to clear search term and results

Basic Usage

Simple Search Component

No results found
import { Search } from '@haus-storefront-react/search'

function MySearchComponent() {
return (
<Search.Root term='' take={5}>
{(searchContext) => (
<div>
<Search.Input placeholder='Search for products...' />
<Search.Clear></Search.Clear>

<Search.Results>
{({ isLoading, items }) => {
if (isLoading) return <div>Loading...</div>
if (!items || items.length === 0)
return <div>No results found</div>

return (
<div>
{items.map((product) => (
<Search.ProductItem
key={product.productId}
product={product}
>
<Search.ProductImage alt={product.productName} />
<div>{product.productName}</div>
</Search.ProductItem>
))}
</div>
)
}}
</Search.Results>
</div>
)}
</Search.Root>
)
}

Basic Hook Usage

import { useSearch } from '@haus-storefront-react/search'

function MyComponent() {
const { data, isLoading, search } = useSearch({
term: '',
take: 5,
groupByProduct: true,
})

if (isLoading) return <div>Loading...</div>
if (!data) return <div>No data</div>

return <div>{/* Render data */}</div>
}

Advanced Usage

Search with Navigation and Price Display

import { Price } from '@haus-storefront-react/common-ui'
import { Search } from '@haus-storefront-react/search'
import { Link, 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.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 null

return (
<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 and price */}
{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, 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>
))}

{/* 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>
)
}

Integration with ProductList

import { ProductList } from '@haus-storefront-react/product-list'
import { useSearchParams } from '@tanstack/react-router'

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>
)
}

Conditional Rendering Patterns

import { Search } from '@haus-storefront-react/search'

function ConditionalSearchComponent() {
return (
<Search.Root term='' take={5}>
{(searchContext) => {
if (searchContext.isLoading) {
return (
<Search.Loading>
<div>Loading...</div>
</Search.Loading>
)
}

if (searchContext.error) {
return <div>Error: {searchContext.error.message}</div>
}

if (!searchContext.data || searchContext.data.items.length === 0) {
return (
<Search.Empty>
<div>No results</div>
</Search.Empty>
)
}

return (
<div>
<Search.Input placeholder='Search...' />
<Search.Results>
{({ items }) => (
<div>
{items?.map((product) => (
<Search.ProductItem
key={product.productId}
product={product}
>
<Search.ProductImage alt={product.productName} />
<div>{product.productName}</div>
</Search.ProductItem>
))}
</div>
)}
</Search.Results>
</div>
)
}}
</Search.Root>
)
}

Made with ❤️ by Haus Tech Team