Build your search interface
Build your search interface
This is for:
DeveloperTo build a Coveo-powered commerce search page, you need to interact with the Search endpoint of the Commerce API. More specifically, you need:
-
A search box for visitors to enter their queries. This search box should call the Request query suggestions endpoint of the Commerce API to provide type ahead suggestions.
It can also call the Request product suggestions endpoint when a query suggestion is hovered to provide instant products for that query.
-
A search results page to display the search results. This page should target the Search endpoint of the Commerce API to retrieve the search results to display. It also handles the response to display the different user interface elements, such as the search results, facet options, and pagination.
While there are several possible approaches for building your search interface, this article focuses on Coveo Headless.
Headless overview
Building a search interface with Coveo Headless involves two main components: a search box and a search page.
Headless provides two types of search box controllers: SearchBox
and StandaloneSearchBox
.
It also offers a Search
controller to manage the search page and results.
-
SearchBox
: Use this controller to create a search box component in your search interface, submit queries, and display query suggestions. This controller is used on the search page itself. -
StandaloneSearchBox
: Use this controller to create a standalone search box that redirects to your search page. This controller is used on every page of your app except the search page. -
Search
: Use this controller to manage search results, such as facets, sorting, and pagination, ensuring a seamless user experience.
A complete example of a search interface is available in the sample project in the Headless repository.
Creating a search box
Using the SearchBox
controller
The following code snippet shows how to create a SearchBox
controller.
import { engine } from './Engine';
import { buildSearchBox } from '@coveo/headless/commerce';
const searchBox = buildSearchBox(engine);
Build the SearchBox controller by passing in the previously initialized engine. |
Next, create a search box component that uses the SearchBox
controller.
import { SearchBox as HeadlessSearchBox } from '@coveo/headless/commerce';
import { useEffect, useState } from 'react';
interface ISearchBoxProps {
controller: HeadlessSearchBox;
}
export default function SearchBox(props: ISearchBoxProps) {
const {controller} = props;
const [state, setState] = useState(controller.state);
useEffect(() => {
controller.state.value && controller.clear();
controller.subscribe(() => setState(controller.state));
}, [controller]);
return (
<div>
<input
value={state.value}
onChange={(e) => controller.updateText(e.target.value)}
></input>
{state.value !== '' && (
<span>
<button onClick={controller.clear}>X</button>
</span>
)}
<button onClick={controller.submit}>Search</button>
{state.suggestions.length > 0 && (
<ul>
{state.suggestions.map((suggestion, index) => (
<li key={index}>
<button
onClick={() => controller.selectSuggestion(suggestion.rawValue)}
dangerouslySetInnerHTML={{__html: suggestion.highlightedValue}}
></button>
</li>
))}
</ul>
)}
</div>
);
}
Subscribe to the state of the SearchBox controller.
This allows you to update the search box component when the state of the controller changes. |
|
Create an input field to allow users to enter their search queries.
As the user types in the search box, call the updateText method on the SearchBox controller to update the query. |
|
Display a clear button when the search box is not empty.
When the user clicks the clear button, call the clear method on the SearchBox controller to clear the search box. |
|
Render a search button that calls the submit method on the SearchBox controller to submit the query. |
|
Display query suggestions when the user types in the search box.
When the user clicks on a suggestion, call the selectSuggestion method on the SearchBox controller to submit the selected suggestion. |
This component can now be included in your search page.
Using the StandaloneSearchBox
controller
In addition to creating a search box component, create a lightweight standalone search box component that utilizes the StandaloneSearchBox
controller to redirect to your search page.
This component should be included on every page of your app except the search page.
Before creating the component, initialize the controller.
import { engine } from './Engine';
import { buildStandaloneSearchBox } from '@coveo/headless/commerce';
const standaloneSearchBox = buildStandaloneSearchBox(engine, {
options: {
redirectionUrl: '/search'
}
});
Build the StandaloneSearchBox controller by passing in the previously initialized engine and the redirection URL to the search page.
When the user submits a query in this search box, the |
Next, create a component that uses this StandaloneSearchBox
controller.
import { StandaloneSearchBox as HeadlessStandaloneSearchBox } from '@coveo/headless/commerce';
import { useEffect, useState } from 'react';
interface IStandaloneSearchBoxProps {
navigate: (url: string) => void;
controller: HeadlessStandaloneSearchBox;
}
export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) {
const {navigate, controller} = props;
const [state, setState] = useState(controller.state);
useEffect(() => {
controller.state.value && controller.clear();
controller.subscribe(() => setState(controller.state));
}, [controller]);
useEffect(() => {
if (state.redirectTo === '/search') {
navigate(`${state.redirectTo}#q=${state.value}`);
controller.afterRedirection();
} else if (state.redirectTo !== '') {
window.location.href = state.redirectTo;
}
}, [state.redirectTo, navigate, state.value, controller]);
return (
<div>
<input
value={state.value}
onChange={(e) => controller.updateText(e.target.value)}
></input>
{state.value !== '' && (
<span>
<button onClick={controller.clear}>X</button>
</span>
)}
<button onClick={() => controller.submit()}>Search</button>
{state.suggestions.length > 0 && (
<ul>
{state.suggestions.map((suggestion, index) => (
<li key={index}>
<button
onClick={() => controller.selectSuggestion(suggestion.rawValue)}
dangerouslySetInnerHTML={{__html: suggestion.highlightedValue}}
></button>
</li>
))}
</ul>
)}
</div>
);
}
Define a navigate function to redirect to the search page. |
|
Subscribe to the state of the StandaloneSearchBox controller. |
|
Redirect to the search page when the user submits a query and the redirectTo property of the controller is set to /search .
Call the afterRedirection method on the controller to reset the state after the redirection. |
|
Create an input field to allow users to enter their search queries. | |
Display a clear button when the search box is not empty. | |
Render a search button that calls the submit method on the StandaloneSearchBox controller to submit the query. |
|
Display query suggestions when the user types in the search box.
When the user clicks on a suggestion, call the selectSuggestion method on the StandaloneSearchBox controller to submit the selected suggestion as a query. |
A complete example, including a sample implementation of the standalone search box component, is available in the Headless repository.
Displaying the search page with the Search
controller
In addition to rendering the search box, you need to display the search results on the search page.
First, initialize the Search
controller.
import { engine } from './Engine';
import { buildSearch } from '@coveo/headless/commerce';
const searchController = buildSearch(engine);
Next, use this controller in a component to render the search page.
import {
Cart,
Search as HeadlessSearch,
ProductListing,
} from '@coveo/headless/commerce';
import { useState, useEffect } from 'react';
import FacetGenerator from '../../facets/facet-generator/facet-generator';
import Pagination from '../../pagination/pagination';
import ProductList from '../../product-list/product-list';
import Sort from '../../sort/sort';
interface ISearchAndListingInterface {
searchOrListingController: HeadlessSearch | ProductListing;
cartController: Cart;
navigate: (pathName: string) => void;
}
export default function SearchAndListingInterface(
props: ISearchAndListingInterface
) {
const {searchOrListingController, cartController, navigate} = props;
const [searchOrListingState, setSearchOrListingState] = useState(
searchOrListingController.state
);
useEffect(() => {
searchOrListingController.subscribe(() =>
setSearchOrListingState(searchOrListingController.state)
);
}, [searchOrListingController]);
return (
<div className="row">
<div className="column">
<FacetGenerator
controller={searchOrListingController.facetGenerator()}
/>
</div>
<div className="column">
<Sort controller={searchOrListingController.sort()} />
<ProductList
products={searchOrListingState.products}
controllerBuilder={searchOrListingController.interactiveProduct}
cartController={cartController}
navigate={navigate}
></ProductList>
<Pagination controller={searchOrListingController.pagination()} />
</div>
</div>
);
}
Note
The previous component can be re-used for both displaying and interacting with results from both search and listing pages. |
A complete example of how to build a search page is available in the Headless repository.
Within the sample project, you can find additional components that were omitted in this article, such as the BreadcrumbManager
(displays a summary of the currently active facet values) and Summary
(provides a summary of search results such as the number of results returned).