Build your search interface

This is for:

Developer

To 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.

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); 1
1 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(() => { 1
    controller.state.value && controller.clear();
    controller.subscribe(() => setState(controller.state));
  }, [controller]);

  return (
    <div>
      <input 2
        value={state.value}
        onChange={(e) => controller.updateText(e.target.value)}
      ></input>
      {state.value !== '' && ( 3
        <span>
          <button onClick={controller.clear}>X</button>
        </span>
      )}
      <button onClick={controller.submit}>Search</button> 4
      {state.suggestions.length > 0 && ( 5
        <ul>
          {state.suggestions.map((suggestion, index) => (
            <li key={index}>
              <button
                onClick={() => controller.selectSuggestion(suggestion.rawValue)}
                dangerouslySetInnerHTML={{__html: suggestion.highlightedValue}}
              ></button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
1 Subscribe to the state of the SearchBox controller. This allows you to update the search box component when the state of the controller changes.
2 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.
3 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.
4 Render a search button that calls the submit method on the SearchBox controller to submit the query.
5 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, { 1
  options: {
    redirectionUrl: '/search'
  }
});
1 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 state.redirectTo is updated to /search. This update can be detected by subscribing to the controller state and then used to redirect the user to the search page.

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; 1
  controller: HeadlessStandaloneSearchBox;
}

export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) {
  const {navigate, controller} = props;

  const [state, setState] = useState(controller.state);

  useEffect(() => { 2
    controller.state.value && controller.clear();
    controller.subscribe(() => setState(controller.state));
  }, [controller]);

  useEffect(() => { 3
    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 4
        value={state.value}
        onChange={(e) => controller.updateText(e.target.value)}
      ></input>
      {state.value !== '' && ( 5
        <span>
          <button onClick={controller.clear}>X</button>
        </span>
      )}
      <button onClick={() => controller.submit()}>Search</button> 6
      {state.suggestions.length > 0 && ( 7
        <ul>
          {state.suggestions.map((suggestion, index) => (
            <li key={index}>
              <button
                onClick={() => controller.selectSuggestion(suggestion.rawValue)}
                dangerouslySetInnerHTML={{__html: suggestion.highlightedValue}}
              ></button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
1 Define a navigate function to redirect to the search page.
2 Subscribe to the state of the StandaloneSearchBox controller.
3 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.
4 Create an input field to allow users to enter their search queries.
5 Display a clear button when the search box is not empty.
6 Render a search button that calls the submit method on the StandaloneSearchBox controller to submit the query.
7 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(() => { 1
    searchOrListingController.subscribe(() =>
      setSearchOrListingState(searchOrListingController.state)
    );
  }, [searchOrListingController]);

  return (
    <div className="row">
      <div className="column">
        <FacetGenerator 2
          controller={searchOrListingController.facetGenerator()}
        />
      </div>
      <div className="column">
        <Sort controller={searchOrListingController.sort()} /> 3
        <ProductList 4
          products={searchOrListingState.products}
          controllerBuilder={searchOrListingController.interactiveProduct}
          cartController={cartController}
          navigate={navigate}
        ></ProductList>
        <Pagination controller={searchOrListingController.pagination()} /> 5
      </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).