Implement filter suggestions and instant products (CSR)
Implement filter suggestions and instant products (CSR)
To improve the functionality of your search box, you can implement filter suggestions and instant products.
-
Filter suggestions are a content discovery feature connected to search boxes and facets. When end-users start typing in a search box, Coveo for Commerce suggests relevant fields and field values to use as filters. The end-user can select one of these filter suggestions to filter the results with a facet on the selected field value.
-
Instant products enhance the search experience by displaying relevant products in real time as users type in the search box. The end-user can then select a product straight from the search box.
This article explains how to implement filter suggestions and instant products in Coveo for Commerce with Coveo Headless in a client-side rendering scenario. It uses an example from the Coveo Headless repository.
Prerequisite
You must have created a Predictive Query Suggestion model or Query Suggestion model, and have implemented query suggestions in your search box.
Enable filter suggestions
|
Filter suggestions in Headless are currently behind a feature flag. Contact your Coveo team to enable it. |
To leverage filter suggestions in Headless, set useInFieldSuggestions
to true
in the target facets in the target search configuration.
See Using the search configuration endpoints.
Only hierarchical and regular facets support filter suggestions.
{
"id": "644e1c29-ae95-4cdc-965b-95e9a2c0d2e5",
// ...
"queryConfiguration": {
"additionalFields": [
"ec_ball_weight",
"ec_traditional_design"
],
"facets": {
"enableIndexFacetOrdering": false,
"freezeFacetOrder": false,
"facets": [
{
"type": "hierarchical",
"facetId": "ec_category",
"field": "ec_category",
"displayNames": [
{
"value": "Categories",
"language": "en"
}
],
"numberOfValues": 10,
"delimitingCharacter": "|",
"basePaths": [],
"filterByBasePath": false,
"useEssentialFilterRuleAsBasePath": false,
"preventAutoSelect": false,
"retrieveCount": 5,
"isFieldExpanded": false,
"useInFieldSuggestions": true
}
// ...
]
},
// ...
},
// ...
}
The useInFieldSuggestions property is set to true for the target facets.
This enables the facet for use in filter suggestions.
You can enable multiple facets for filter suggestions. |
Headless implementation
Implement these features in Coveo Headless as follows:
-
Use the
FilterSuggestionsGenerator
controller to access and display filter suggestions. The Coveo Headless repository contains a sample implementation of filter suggestions. -
Use the
InstantProducts
controller to access and display instant products. TheInstantProducts
controller in the Coveo Headless repository contains a sample implementation of instant products.
This section explains the key parts of the implementation.
Initialize the target controllers
In the pages where you include your search box and your standalone search box, initialize the InstantProducts
and FilterSuggestionsGenerator
controllers and pass them to the search box and standalone search box components.
See the layout.tsx file in the sample project for a full example.
// src/layout/layout.tsx
// ...
const standaloneSearchBoxId = 'standalone-search-box';
// ...
<StandaloneSearchBox
navigate={navigate}
controller={buildStandaloneSearchBox(engine, {
options: {
redirectionUrl: '/search',
id: standaloneSearchBoxId,
highlightOptions,
},
})}
instantProductsController={buildInstantProducts(
engine
)}
filterSuggestionsGeneratorController={buildFilterSuggestionsGenerator(
engine
)}
/>
// ...
Uses the buildInstantProducts function to create the InstantProducts controller. |
|
Uses the buildFilterSuggestionsGenerator function to create the FilterSuggestionsGenerator controller. |
Implement a filter suggestion generator component
To display filter suggestions in your search box components, implement a component that displays the filter suggestions using the FilterSuggestions
controller.
// src/components/filter-suggestions/filter-suggestions-generator.tsx
import {
FilterSuggestionsGenerator as HeadlessFilterSuggestionsGenerator,
FilterSuggestions as HeadlessFilterSuggestions,
CategoryFilterSuggestions,
RegularFacetSearchResult,
CategoryFacetSearchResult,
} from '@coveo/headless/commerce';
import {useEffect, useState} from 'react';
import FilterSuggestions from './filter-suggestions.js';
interface IFilterSuggestionsGeneratorProps {
controller: HeadlessFilterSuggestionsGenerator;
onClickFilterSuggestion: (
controller: HeadlessFilterSuggestions | CategoryFilterSuggestions,
value: RegularFacetSearchResult | CategoryFacetSearchResult
) => void;
}
export default function FilterSuggestionsGenerator(
props: IFilterSuggestionsGeneratorProps
) {
const {controller, onClickFilterSuggestion} = props;
const [filterSuggestionsState, setFilterSuggestionsState] = useState(
controller.filterSuggestions
);
useEffect(() => {
controller.subscribe(() => {
setFilterSuggestionsState(controller.filterSuggestions);
});
}, [controller]);
if (filterSuggestionsState.length === 0) {
return null;
}
return (
<div className="FilterSuggestionsGenerator">
{filterSuggestionsState.map((filterSuggestionsController) => {
return (
<FilterSuggestions
key={filterSuggestionsController.state.facetId}
controller={filterSuggestionsController}
onClickFilterSuggestion={onClickFilterSuggestion}
/>
);
})}
</div>
);
}
The FilterSuggestions component will display the filter suggestions for a given facet.
You’ll define it just below. |
|
The onClickFilterSuggestion function is called when the user clicks a filter suggestion.
It’s passed as a prop and you’ll define it below. |
The FilterSuggestions
component is defined as follows.
// src/components/filter-suggestions/filter-suggestions.tsx
import {
CategoryFacetSearchResult,
CategoryFilterSuggestions,
FilterSuggestions as HeadlessFilterSuggestions,
RegularFacetSearchResult,
} from '@coveo/headless/commerce';
import {useEffect, useState} from 'react';
interface IFilterSuggestionsProps {
controller: HeadlessFilterSuggestions | CategoryFilterSuggestions;
onClickFilterSuggestion: (
controller: HeadlessFilterSuggestions | CategoryFilterSuggestions,
value: RegularFacetSearchResult | CategoryFacetSearchResult
) => void;
}
export default function FilterSuggestions(props: IFilterSuggestionsProps) {
const {controller, onClickFilterSuggestion} = props;
const [state, setState] = useState(controller.state);
useEffect(() => {
controller.subscribe(() => {
setState(controller.state);
});
}, [controller]);
if (state.values.length === 0) {
return null;
}
const renderFilterSuggestionButton = (
value: RegularFacetSearchResult | CategoryFacetSearchResult
) => {
return (
<button onClick={() => onClickFilterSuggestion(controller, value)}>
Search for <em>{state.query}</em>{' '}
{controller.type === 'hierarchical' ? 'in' : 'with'}{' '}
<b>{state.displayName}</b> <em>{value.displayValue}</em> ({value.count}{' '}
products)
</button>
);
};
return (
<div className="FilterSuggestions">
<p>
<b>{state.displayName}</b> suggestions
</p>
<ul>
{state.values.map((value) => (
<li
key={
'path' in value
? [...value.path, value.rawValue].join(';')
: value.rawValue
}
>
{renderFilterSuggestionButton(value)}
</li>
))}
</ul>
</div>
);
}
This component supports both hierarchical and regular facets. |
Implement an instant products component
To display instant products in your search box components, implement a component that displays the instant products using the InstantProducts
controller.
// src/components/instant-products/instant-products.tsx
import {
InstantProducts as HeadlessInstantProducts,
Product,
} from '@coveo/headless/commerce';
import {useEffect, useState} from 'react';
interface IInstantProductProps {
controller: HeadlessInstantProducts;
navigate: (pathName: string) => void;
}
export default function InstantProducts(props: IInstantProductProps) {
const {controller, navigate} = props;
const [state, setState] = useState(controller.state);
useEffect(
() => controller.subscribe(() => setState({...controller.state})),
[controller]
);
if (state.products.length === 0 || !state.query) {
return null;
}
const onClickProduct = (product: Product) => {
controller.interactiveProduct({options: {product}}).select();
// navigate to the product page
};
return (
<div className="InstantProducts">
{state.products.length === 0 ? (
<p className="NoInstantProducts">
No instant products for query <b>{state.query}</b>
</p>
) : (
<>
<p>
Instant products for query <b>{state.query}</b>
</p>
<ul className="InstantProducts">
{state.products.map((product, index) => (
<li className="Product" key={index}>
<button onClick={() => onClickProduct(product)}>
{product.ec_name} ({product.ec_product_id})
</button>
</li>
))}
</ul>
</>
)}
</div>
);
}
Use your components in your search box
In your search box and standalone search box components, use the FilterSuggestionsGenerator
component to display the filter suggestions and the InstantProducts
component to display the instant products.
For example, see the standalone-search-box.tsx file in the sample project.
// src/components/standalone-search-box/standalone-search-box.tsx
// ...
interface IStandaloneSearchBoxProps {
navigate: (url: string) => void;
ssbController: HeadlessStandaloneSearchBox;
instantProductsController: HeadlessInstantProducts;
filterSuggestionsGeneratorController: HeadlessFilterSuggestionsGenerator;
}
export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) {
const {
navigate,
ssbController,
instantProductsController,
filterSuggestionsGeneratorController,
} = props;
const [ssbState, setSsbState] = useState(ssbController.state);
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
// ...
const fetchFilterSuggestions = (value: string) => {
for (const filterSuggestions of filterSuggestionsGeneratorController.filterSuggestions) {
filterSuggestions.updateQuery(value);
}
};
const clearFilterSuggestions = () => {
for (const filterSuggestions of filterSuggestionsGeneratorController.filterSuggestions) {
filterSuggestions.clear();
}
};
// ...
const onSearchBoxInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// ...
ssbController.updateText(e.target.value);
// ...
fetchFilterSuggestions(e.target.value);
instantProductsController.updateQuery(e.target.value);
};
const onSearchBoxInputKeyDown = (
e: React.KeyboardEvent<HTMLInputElement>
) => {
switch (e.key) {
case 'Escape':
// ...
if (ssbState.value !== '') {
// ...
clearFilterSuggestions();
instantProductsController.updateQuery('');
break;
}
break;
// ...
}
};
const onClickSearchBoxClear = () => {
// ...
clearFilterSuggestions();
instantProductsController.updateQuery(state.value);
};
const onFocusSuggestion = (suggestion: Suggestion) => {
// ...
fetchFilterSuggestions(suggestion.rawValue);
instantProductsController.updateQuery(suggestion.rawValue);
};
// ...
const renderDropdown = () => {
return (
// ...
<FilterSuggestionsGenerator
controller={filterSuggestionsGeneratorController}
onClickFilterSuggestion={(
controller: FilterSuggestions | CategoryFilterSuggestions,
value: RegularFacetSearchResult | CategoryFacetSearchResult
) => {
hideDropdown();
const parameters =
controller.type === 'hierarchical'
? controller.getSearchParameters(
value as CategoryFacetSearchResult
)
: controller.getSearchParameters(
value as RegularFacetSearchResult
);
navigate(`/search#${parameters}`);
}}
//...
/>
<div className="InstantProducts column small">
<InstantProducts
controller={instantProductsController}
navigate={navigate}
/>
</div>
)
};
return (
<div className="Searchbox">
<input
className="SearchBoxInput"
onChange={onSearchBoxInputChange}
onKeyDown={onSearchBoxInputKeyDown}
ref={searchInputRef}
value={ssbState.value}
/>
<button
className="SearchBoxClear"
disabled={
ssbState.isLoadingSuggestions || ssbState.isLoading || ssbState.value === ''
}
onClick={onClickSearchBoxClear}
type="reset"
>
// ...
{isDropdownVisible && renderDropdown()}
</div>
);
</div>
Defines a function to fetch filter suggestions. | |
Similarly, defines a function to clear the filter suggestions. | |
The onSearchBoxInputChange function is called when the user types in the search box.
Update it to call fetchFilterSuggestions after the standalone search box controller has updated the engine state with the new query. |
|
Similarly, update the instantProductsController with the new query. |
|
Use the clearFilterSuggestions functions to clear and fetch the filter suggestions when the user selects the Escape key.
Also, update the instantProductsController with an empty query.
Similar logic applies below for other user interactions, such as selecting the clear button. |
|
The renderDropdown function is called to render the filter suggestions dropdown menu.
Display the FilterSuggestionsGenerator and InstantProducts components in the dropdown menu. |
|
Define the function to handle the click on a filter suggestion and pass it to the FilterSuggestionsGenerator component.
It hides the dropdown menu and navigates to the search page with the chosen filter value selected. |
|
Since this is a standalone search box, the navigate function is used to navigate to the search page with the selected filter. |
|
When users change the search box input, onSearchBoxInputChange is called, which updates the filter suggestions by calling fetchFilterSuggestions .
Also, update the instantProductsController with the new query.
Similar logic applies below for other other interactions, such as selecting the Escape key or selecting the clear button. |