Search Headless | Yext Hitchhikers Platform

Yext has developed a series of tools designed to make UI development with the Search API easier. This includes a series of components that can be added to any React application.

Under the hood, the components are powered by Search Headless React; a library that manages data returned by the Search API in a global SearchHeadless instance. This is implemented with Redux, “a predictable state container for JavaScript apps” . There’s a lot of boilerplate code that comes with setting up Redux for calling APIs based on user-triggered events and modifying the state accordingly.

Search Headless React removes the complexity of having to write all the boilerplate for Redux + Search API. It also comes with a series of custom hooks for rendering Search state data in the UI and changing the Search state based on events triggered by the components.

This article introduces Search Headless React, the hooks that come with it, and how the Search UI React components use headless.

Check out the appendix section if you want to learn more about Redux.

Search Headless React: State and Hooks

Search Headless React

If you were to develop a Redux state management solution for the Search API you have to do something resembling the following steps:

  1. Design the shape of your Redux store that will need to accommodate the search state of your application.
  2. Write API client functions for calling the Search APIs that you want to use in your app which could include search queries, autocomplete, and filter search.
  3. Create the action functions that will call the API client functions before dispatching actions.
  4. Create the reducer functions responsible for modifying and returning a new copy of the redux store

Fortunately, Search Headless React takes care of all of this setup for you. It also offers it’s own provider component and hooks that are conceptually similar to useSelector and useDispatch.

SearchHeadlessProvider and provideHeadless

To use the Search Headless React hooks, components need to be children of a <SearchHeadlessProvider />. The provider requires a prop called searcher which is an instance of the SearchHeadless state. To instantiate a new SearchHeadless instance, you need to use the provideHeadless hook.

The searchHeadless requires a HeadlessConfig argument with has a few mandatory fields:

  • apiKey → the unique Search API key for your Yext account
  • experienceKey → the unique key for a specific Search experience
  • locale → locale (language) of the Search experience

Under the hood, these props are for authenticating the API calls made by Search Headless to the Search API. Any nested component within the <SearchHeadlessProvider /> can use the Search Headless hooks.

import {
  SearchHeadlessProvider,
  provideHeadless,
} from "@yext/search-headless-react";
// ...other imports

const searcher = provideHeadless({
  apiKey: "YOUR_API_KEY",
  experienceKey: "YOUR_EXPERIENCE_KEY",
  locale: "en",
});

ReactDOM.render(
  <React.StrictMode>
    <SearchHeadlessProvider searcher={searcher}>
      <App />
    </SearchHeadlessProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

useSearchState

Similar to React Redux’s useSelector hook, the useSearchState hook is designed to extract data from the search store for usage by the component. The Search store is passed as an argument and a specified part of the store is returned. Just like the useSelector hook, useSearchState will run when the component is rendered or when a Search action is dispatched.

This example shows how you could use the hook to read a value from the state and render it in a component:

import { useSearchState } from "@yext/search-headless-react";

export default function MostRecentSearch() {
  const mostRecentSearch = useSearchState(
    (state) => state.query.mostRecentSearch
  );
  return <div>Showing results for {mostRecentSearch}</div>;
}

You can find all the Search properties exposed by the state object here .

useSearchActions

The useSearchActions hook allows for your components to dispatch actions like searching, autocomplete, and applying facets. Just like useDispatch, you can use functions from this hook to trigger search actions from events triggered by your components.

The example below is an input element that calls the useSearchActions setQuery function to update the search query in the store each time the user enters a key stroke and calls the executeUniversalQuery function when the enter key is pressed.

import { useSearchActions } from "@yext/search-headless-react";
import { ChangeEvent, KeyboardEvent, useCallback } from "react";

function SearchBar() {
  const search = useSearchActions();
  const handleTyping = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => {
      search.setQuery(e.target.value);
    },
    [search]
  );

  const handleKeyDown = useCallback(
    (evt: KeyboardEvent<HTMLInputElement>) => {
      if (evt.key === "Enter") {
        search.executeUniversalQuery();
      }
    },
    [search]
  );

  return <input onChange={handleTyping} onKeyDown={handleKeyDown} />;
}

You can find all the Search actions functions here .

Developing Components with Headless React

Search Headless React has everything you need to create your own custom UI search components.

Building some things like search bars with autocomplete are complex and time consuming. You need to consider:

  • Calling the executeAutocompleteSearch action on every keystroke
  • Re-rendering the autocomplete dropdown each time the autocomplete state changes
  • Event handling for clicks on the autocomplete results, search icon, and X icon

You can to develop this by yourself, but we recommend importing the <SearchBar /> component from @yext/search-ui-react. Then, import the <VerticalResults /> component to render the vertical search results.

Take a look at the source code for <VerticalResults/> and <SearchBar /> to see how they are using the Search Headless React hooks.