Skip to main content

SearchBar

A self-contained search bar with autocomplete dropdown. Fetches product results internally via ProductService and displays them with images, prices, and SKUs. Supports debounced input, configurable result limits, and click-outside dismissal.

Usage

Basic usage in a header

import SearchBar from '@/components/propeller/SearchBar';
import { graphqlClient } from '@/lib/api';
import { useRouter } from 'next/navigation';

function Header() {
const router = useRouter();

return (
<SearchBar
graphqlClient={graphqlClient}
onSubmit={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
onResultClick={(result) => result.url && router.push(result.url)}
onViewAllClick={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
/>
);
}

With custom language, fallback image, and price formatter

<SearchBar
graphqlClient={graphqlClient}
language="EN"
placeholder="Search our catalog..."
noImageUrl="/images/no-image.webp"
formatPrice={(price) => `$${price.toFixed(2)}`}
onSubmit={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
onResultClick={(result) => result.url && router.push(result.url)}
onViewAllClick={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
/>

With stricter debounce and fewer results

<SearchBar
graphqlClient={graphqlClient}
minSearchLength={2}
debounceMs={500}
maxResults={5}
labels={{
viewAll: 'Show all products',
noResults: 'Nothing found for',
}}
onSubmit={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
onResultClick={(result) => result.url && router.push(result.url)}
onViewAllClick={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
/>

Custom container styling

<SearchBar
graphqlClient={graphqlClient}
containerClassName="relative w-full max-w-lg"
onSubmit={(term) => router.push(`/search/${encodeURIComponent(term)}`)}
onResultClick={(result) => result.url && router.push(result.url)}
/>

Configuration

Required

PropTypeDescription
graphqlClientGraphQLClientPropeller SDK GraphQL client instance. The component creates ProductService internally.

Search Configuration

PropTypeDefaultDescription
languagestring'NL'Language code sent with search requests.
placeholderstring'Search products...'Placeholder text for the input field.
minSearchLengthnumber3Minimum characters typed before a search request fires.
debounceMsnumber300Milliseconds to wait after the last keystroke before searching.
maxResultsnumber8Maximum number of suggestion results shown in the dropdown.

Display

PropTypeDefaultDescription
noImageUrlstring''Fallback image URL when a result has no product image. When empty, broken image icons may appear for products without images.
formatPrice(price: number) => stringFormats as €{price}Custom price formatting function.
labelsRecord<string, string>See Labels tableCustomizable UI text strings.
containerClassNamestring'relative flex-1 max-w-2xl mx-8'CSS class for the outermost container element.

Callbacks

PropTypeDescription
onSubmit(term: string) => voidFires when the form is submitted (Enter key). Receives the trimmed search term.
onResultClick(result: SearchBarResult) => voidFires when a dropdown result is clicked. Receives the SearchBarResult object.
onViewAllClick(term: string) => voidFires when "View all results" is clicked. Receives the current search term.

SearchBarResult Interface

The result object passed to onResultClick and used internally:

interface SearchBarResult {
id: number | string;
name: string;
sku?: string;
price?: number;
imageUrl?: string;
url?: string;
isCluster?: boolean;
}

Labels

KeyDefaultDescription
viewAll'View all results'Text for the "view all" link at the bottom of the dropdown.
noResults'No products found for'Prefix text shown when no results match the query.

Behavior

Debounce and minimum characters

The component waits debounceMs (default 300ms) after the user stops typing before making a search request. No request is made until at least minSearchLength (default 3) characters have been entered. If the user clears the input below the minimum length, the dropdown closes immediately.

Image variants

Search results request 100x100 WEBP thumbnails via the imageVariantFilters parameter. The imageVariantFilters must contain a transformations array -- passing an empty object {} causes the Propeller API to return an error. If a result has no image and noImageUrl is provided, the fallback image is shown instead.

When a user clicks a result, the component:

  1. Calls onResultClick(result) with the full SearchBarResult object
  2. Closes the dropdown
  3. Clears the search input

Result URLs are auto-generated as /cluster/{id}/{slug} for clusters or /product/{id}/{slug} for products. The parent component handles actual navigation in the onResultClick callback.

Form submission

Pressing Enter submits the form and calls onSubmit with the trimmed search term. This is typically used to navigate to a full search results page.

View all results

When the total number of matching items (itemsFound) exceeds maxResults, a "View all results (N)" link appears at the bottom of the dropdown. Clicking it calls onViewAllClick with the current search term.

Click outside dismissal

The dropdown closes automatically when the user clicks anywhere outside the search bar. This is handled via a mousedown listener on document.

Loading state

A spinning indicator appears in the input field while a search request is in progress.


GraphQL Query

Under the hood, ProductService.getProducts() executes a query similar to:

query SearchProducts($input: ProductSearchInput!, $imageSearchFilters: ImageSearchInput, $imageVariantFilters: ImageVariantSearchInput) {
products(input: $input) {
itemsFound
items {
productId
clusterId
sku
names { value }
slugs { value }
price { gross net }
defaultProduct {
sku
price { gross net }
media {
images(input: $imageSearchFilters) {
items {
imageVariants(input: $imageVariantFilters) {
url
}
}
}
}
}
media {
images(input: $imageSearchFilters) {
items {
imageVariants(input: $imageVariantFilters) {
url
}
}
}
}
}
}
}

Variables sent by the component:

{
input: {
term: "search text",
language: "NL",
page: 1,
offset: 8,
statuses: ["A", "P", "T", "S"],
hidden: false,
sortInputs: [{ field: "RELEVANCE", order: "DESC" }],
},
imageSearchFilters: { page: 1, offset: 1 },
imageVariantFilters: {
transformations: [{
name: "thumb",
transformation: {
format: "WEBP",
height: 100,
width: 100,
fit: "BOUNDS",
},
}],
},
}

IMPORTANT: imageVariantFilters must NOT be an empty object {}. The Propeller API rejects empty variant filters. You must always include a transformations array with at least one entry.


SDK Services

The component uses ProductService from propeller-sdk-v2 to fetch search suggestions. It creates the service instance internally:

const productService = new ProductService(graphqlClient);
const response = await productService.getProducts({ input, imageSearchFilters, imageVariantFilters });

The response items (which can be Product or Cluster objects) are mapped to SearchBarResult objects. For clusters, the defaultProduct is used for price and image data.

Search parameters

The component searches with these fixed parameters:

  • Statuses: A (active), P (published), T (temporary), S (special)
  • Sort: RELEVANCE descending
  • Hidden: false
  • Searchable attributes only: isSearchable: true
  • Pagination: page 1, offset = maxResults