Build search interfaces (Shopify Hydrogen)

This is for:

Developer

Building a search interface with Coveo Headless and Shopify Hydrogen involves three main components: a search box, a search page, and a search provider.

The Coveo Headless React package provides two types of search box controllers: the SearchBox and the StandaloneSearchBox. It also exposes functions to create a search provider, which you can use to manage the search page and results.

Prerequisite

Make sure that you understand how to build commerce search pages.

To create a search box, use the SearchBox and StandaloneSearchBox controllers:

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

Define the controllers

Create a component that uses the SearchBox controller for the search page and a lightweight standalone search box component that uses the StandaloneSearchBox controller to redirect to your search page. This standalone search box component should be included on every page of your app except the search page.

When defining your commerce engine, define both search box controllers.

    searchBox: defineSearchBox(), 1
    standaloneSearchBox: defineStandaloneSearchBox({
      options: {redirectionUrl: '/search', id: 'standalone-search-box'},
    }),
1 The searchBox controller powers a search input on the search results page. The standaloneSearchBox is for pages that redirect to search.

Define the SearchBox controller via the defineSearchBox function.

Define the StandaloneSearchBox controller via the defineStandaloneSearchBox function and specify the redirection URL. 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.

Using the controllers

The following example shows how to create a component using the StandaloneSearchBox controller.

The SearchBox component isn’t included in this article, but it follows a similar implementation without the redirection logic.

// app/components/StandaloneSearchBox.tsx

import {useNavigate} from '@remix-run/react';
import {useEffect, useRef} from 'react';
import {useStandaloneSearchBox} from '~/lib/commerce-engine';

export function StandaloneSearchBox() {
  const searchBox = useStandaloneSearchBox();
  const inputRef = useRef<HTMLInputElement>(null);
  const navigate = useNavigate();

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  useEffect(() => {
    if (searchBox.state.redirectTo === '/search') {
      navigate(
        `${searchBox.state.redirectTo}?q=${encodeURIComponent(
          searchBox.state.value
        )}`
      );
    }
  }, [searchBox.state.redirectTo, searchBox.state.value, navigate]);

  const handleSuggestionClick = (suggestion: string) => {
    searchBox.methods?.updateText(suggestion);
    inputRef.current!.value = suggestion;
    searchBox.methods?.showSuggestions();
  };

  return (
    <div>
      <input
        ref={inputRef}
        aria-label="Search"
        placeholder="Search"
        onChange={(e) => searchBox.methods?.updateText(e.target.value)}
        onFocus={() => searchBox.methods?.showSuggestions()}
      />
      <button type="button" onClick={searchBox.methods?.submit}>
        Search
      </button>

      {searchBox.state.suggestions.length > 0 && (
        <div>
          {searchBox.state.suggestions.map((suggestion) => (
            <button
              type="button"
              key={suggestion.rawValue}
              onClick={() => handleSuggestionClick(suggestion.rawValue)}
            >
              {suggestion.highlightedValue}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

Displaying the search page with the search provider

Once the search box is set up, you must display the search results on the search page, using your search provider, which manages facets, sorting, and pagination to ensure a seamless user experience.

For your search page route, add the following code to render the search page with the search provider.

export async function loader({request}: LoaderFunctionArgs) { 1
  const navigatorContextProvider = () =>
    new ServerSideNavigatorContextProvider(request);

  searchEngineDefinition.setNavigatorContextProvider(navigatorContextProvider); 2
  standaloneEngineDefinition.setNavigatorContextProvider(
    navigatorContextProvider
  );

  const url = new URL(request.url);
  const {deserialize} = buildParameterSerializer();
  const parameters = deserialize(url.searchParams); 3
  const query = url.searchParams.get('q') || '';

  const searchStaticState = await fetchStaticState({
    k: 'searchEngineDefinition',
    url: request.url, 4
    parameters,
    q: query || undefined,
  });

  return {searchStaticState, query};
}
Search loader annotations
1 Use the loader function to run server-side code.
2 Set the navigator context provider to the server-side context provider.
3 Fetch the deserialized search parameters from the URL.
4 Fetch the static state for the search page.
export default function SearchPage() {
  const {searchStaticState} = useLoaderData<typeof loader>();
  const location = useLocation();
  const currentUrl = `${location.pathname}${location.search}${location.hash}`;

  return (
    <SearchProvider 1
      navigatorContext={new ClientSideNavigatorContextProvider()}
      staticState={searchStaticState as SearchStaticState}
    >
      <ParameterManager url={currentUrl} />
      <SearchBox />
      <SearchSummary />
      <Sort />
      <Facets />
      <ProductList />
      <Pagination />
    </SearchProvider>
  );
}
Search component annotations
1 Wrap the search page with the SearchProvider component.

The SearchBox component will be similar to the standalone search box component, but it will use the SearchBox controller instead of the StandaloneSearchBox controller and not include the redirection logic.

For more details on facets, sorting, and pagination, see Leveraging facets, sorting, and pagination.

The BreadcrumbManager can also be used to display a summary of the currently active facet values.