Setting Up Coveo Headless Standalone Search Box

August 26, 2022

By David Austin

Purpose of a Standalone Search Box

When you utilize Coveo and build custom interfaces one thing that will stand out is that of the Search Box. In particular, the fact there are two ways of implementing a search box. A normal search box that's included within a search page and that of a standalone version.

The main difference is that the Standalone version is fully independent and doesn't interact directly with things like results and facets. Its sole purpose is to take in a search query, present potential Query Suggestions (if applicable), and the redirect the query to the search result page.

In a Headless environment, this is achieved using local storage to store the query-related information before it's redirected to the search results. The Standalone Search Box effectively never executes a search query.

Let's Build It

The first thing to do is to set up the initialization of the Standalone Search Box. There aren't many options, but you can find the full list here: StandaloneSearchBoxOptions.

We won't be going into how to display Query Suggestions this time around but hope to in a future article.

Setup Search Box Initialization

The initialization of the standalone search box is relatively clean and simple. We use the HeadlessEngineContext we created previously. Once done, we pass the controller we obtained from buildStandaloneSearchBox to our renderer function. You could very well combine these two if so desired.

const StandaloneSearchBox = (props: any) => {
  const options: StandaloneSearchBoxOptions = {
    numberOfSuggestions: props.numOfPages,
    redirectionUrl: props.searchPage,
  };
  const engine = useContext(HeadlessEngineContext) as unknown as SearchEngine;
  const controller = buildStandaloneSearchBox(engine, { options });
  return (
    <SearchBoxRenderer
      engine={engine}
      controller={controller}
      searchPage={props.searchPage as string}
    />
  );
};

Setup Renderer Function

As part of the renderer, we've added two buttons that assist with both the submission as well as the clearing of the query. For those we've used react-feather to display the icons.

Within the if statement for redirectTo, if true, we store the query value and analytics into local storage to be read by the result page.


  interface StandaloneSearchBoxProps {
    engine: SearchEngine;
    controller: HeadlessSearchBox;
    searchPage: string;
  }

  const SearchBoxRenderer: FunctionComponent<StandaloneSearchBoxProps> = (props) => {
    const { controller } = props;
    const [state, setState] = useState(controller.state);

    // Ensure we detect users who hit Enter key.
    const isEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => e.key === 'Enter';
  
    // Need function when a query is Submitted.
    const submitQuery = (): void => {
      if (!state) {
        return;
      }

      const { redirectTo, value, analytics } = state;
  
      // If there is a redirectTo value, this indicates we want to send the user to the result page.
      if (redirectTo) {
        const data = { value, analytics };
        localStorage.setItem('coveo_standalone_search_box_data', JSON.stringify(data));
        window.location.href = state.redirectTo;
        return;
      }
    };
  
    // What to do when user clicks to clear query.
    const clearQuery = (): void => {
      if (!state) {
        return;
      }
      controller.updateText('');
    };
  
    submitQuery();
  
    useEffect(() => controller.subscribe(() => setState(controller.state)), []);
  
    return (
      <div>
        <input
          type={'text'}
          value={state.value}
          onChange={(e) => {
            controller.updateText(e.target.value);
            showQuerySummary(e.target.value);
          }}
          onKeyDown={(e) => isEnterKey(e) && controller.submit()}
          className={props.searchBoxStyling.inputField}
        />
        <div
          id={'search-icon'}
          onClick={() => submitQuery()}
          tabIndex={0}
          onKeyPress={(e) => (e.key === 'Enter' ? submitQuery() : null)}
        >
          <Search />
        </div>
        <div
          id={'cancel-search-icon'}
          onClick={() => clearQuery()}
          tabIndex={0}
          onKeyPress={(e) => (e.key === 'Enter' ? clearQuery() : null)}
        >
          <X />
        </div>
      </div>
    );
  };

Within the returned DOM we have an input box, a search button, and a clear button. Each has appropriate onChange, onClick, or onKeyPress events that trigger the appropriate responses.

As I mentioned above, this is a very simple Standalone Search Box. You can of course expand upon it to display results from the Query Suggestion model if it's being utilized.

Image of Fishtank employee David Austin

David Austin

Development Team Lead | Sitecore Technology MVP x 3

David is a decorated Development Team Lead with Sitecore Technology MVP and Coveo MVP awards, as well as Sitecore CDP & Personalize Certified. He's worked in IT for 25 years; everything ranging from Developer to Business Analyst to Group Lead helping manage everything from Intranet and Internet sites to facility management and application support. David is a dedicated family man who loves to spend time with his girls. He's also an avid photographer and loves to explore new places.