---
title: Integrate instant products and filter suggestions with a search box (CSR)
slug: q58b4563
canonical_url: https://docs.coveo.com/en/q58b4563/
collection: coveo-for-commerce
source_format: adoc
---
# Integrate instant products and filter suggestions with a search box (CSR)

After building your [instant products](https://docs.coveo.com/en/q58b4520.md) and [filter suggestions](https://docs.coveo.com/en/q58b4295.md) components, integrate them into your search box to create a rich search-as-you-type experience.

This is the final step of the [instant products and filter suggestions guide](https://docs.coveo.com/en/o8ce0240.md).

## Initialize the target controllers

In the pages where you include your search box, initialize the `InstantProducts` and `FilterSuggestionsGenerator` controllers and pass them to the search box component.

See the [layout.tsx](https://github.com/coveo/ui-kit/tree/main/samples/headless/commerce-react/src/layout/layout.tsx) file in the sample project for a full example.

```tsx

// src/layout/layout.tsx

import {

  buildFilterSuggestionsGenerator,

  buildInstantProducts, <1>

  buildStandaloneSearchBox,

  type CommerceEngine,

} from '@coveo/headless/commerce';

import StandaloneSearchBox from '../components/standalone-search-box/standalone-search-box.js';



const highlightOptions = {

  notMatchDelimiters: {open: '<span>', close: '</span>'},

  correctionDelimiters: {open: '<i>', close: '</i>'},

};



interface ILayoutProps {

  engine: CommerceEngine;

  navigate: (url: string) => void;

}



export default function Layout(props: ILayoutProps) {

  const {engine, navigate} = props;

  const standaloneSearchBoxId = 'standalone-search-box';



  return (

    <div className="Layout">

      <h1>Coveo Headless Commerce — Search Box Features</h1>

      <StandaloneSearchBox

        navigate={navigate}

        controller={buildStandaloneSearchBox(engine, {

          options: {

            redirectionUrl: '/search',

            id: standaloneSearchBoxId,

            highlightOptions,

          },

        })}

        instantProductsController={buildInstantProducts(engine, {

          options: {searchBoxId: standaloneSearchBoxId},

        })} <2>

        filterSuggestionsGeneratorController={buildFilterSuggestionsGenerator( <3>

          engine

        )}

      />

    </div>

  );

}

```

<1> Uses the `buildInstantProducts` function to create the `InstantProducts` controller.

<2> Pass a `searchBoxId` option to tie instant products to this specific search box.

<3> Uses the `buildFilterSuggestionsGenerator` function to create the `FilterSuggestionsGenerator` controller.
## Use your components in your search box

In your standalone search box component, use the `InstantProducts` component to display the instant products and the `FilterSuggestionsGenerator` component to display the filter suggestions.

See the [standalone-search-box.tsx](https://github.com/coveo/ui-kit/tree/main/samples/headless/commerce-react/src/components/standalone-search-box/standalone-search-box.tsx) file in the sample project.

### Set up imports and props

Start by importing the required Headless controllers and child components.
The search box component accepts the `StandaloneSearchBox`, `InstantProducts`, and `FilterSuggestionsGenerator` controllers as props, along with a `navigate` function for routing.

```tsx

import type {

  CategoryFacetSearchResult,

  CategoryFilterSuggestions,

  FilterSuggestions,

  FilterSuggestionsGenerator as HeadlessFilterSuggestionsGenerator,

  InstantProducts as HeadlessInstantProducts,

  StandaloneSearchBox as HeadlessStandaloneSearchBox,

  RegularFacetSearchResult,

  Suggestion,

} from '@coveo/headless/commerce';

import {useEffect, useRef, useState} from 'react';

import FilterSuggestionsGenerator from '../filter-suggestions/filter-suggestions-generator.js';

import InstantProducts from '../instant-products/instant-products.js';

import './standalone-search-box.css';



interface IStandaloneSearchBoxProps {

  navigate: (url: string) => void;

  controller: HeadlessStandaloneSearchBox;

  instantProductsController: HeadlessInstantProducts;

  filterSuggestionsGeneratorController: HeadlessFilterSuggestionsGenerator;

}

```
### Manage component state

Subscribe to the `StandaloneSearchBox` controller state to keep the component in sync, and track whether the dropdown is visible.

```tsx

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

const [isDropdownVisible, setIsDropdownVisible] = useState(false);

const searchInputRef = useRef<HTMLInputElement>(null);



useEffect(() => {

  controller.subscribe(() => setState({...controller.state}));

}, [controller]);



const hideDropdown = () => setIsDropdownVisible(false);

const showDropdown = () => setIsDropdownVisible(true);

```
### Define filter suggestion helpers

Create helper functions that iterate over all `FilterSuggestions` sub-controllers to update or clear filter suggestions whenever the query changes.

```tsx

const fetchFilterSuggestions = (value: string) => { <1>

  for (const filterSuggestions of filterSuggestionsGeneratorController.filterSuggestions) {

    filterSuggestions.updateQuery(value);

  }

};



const clearFilterSuggestions = () => { <2>

  for (const filterSuggestions of filterSuggestionsGeneratorController.filterSuggestions) {

    filterSuggestions.clear();

  }

};

```

<1> Define a function to fetch filter suggestions by updating the query on each `FilterSuggestions` sub-controller.

<2> Define a function to clear all filter suggestions.
### Handle user input

Define handlers for text input, keyboard events, clearing the search box, and interacting with query suggestions.
Each handler coordinates the `StandaloneSearchBox`, `InstantProducts`, and `FilterSuggestionsGenerator` controllers so that all features update together.

```tsx

const onSearchBoxInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {

  if (e.target.value === '') {

    hideDropdown();

    controller.clear();

    return;

  }



  controller.updateText(e.target.value);

  controller.showSuggestions();

  instantProductsController.updateQuery(e.target.value); <1>

  fetchFilterSuggestions(e.target.value); <2>

  showDropdown();

};



const onSearchBoxInputKeyDown = (

  e: React.KeyboardEvent<HTMLInputElement>

) => {

  switch (e.key) {

    case 'Escape':

      if (isDropdownVisible) {

        hideDropdown();

        break;

      }

      if (state.value !== '') {

        controller.clear();

        clearFilterSuggestions(); <3>

        instantProductsController.updateQuery('');

        break;

      }

      break;

    case 'Enter':

      hideDropdown();

      controller.submit();

      break;

  }

};



const onClickSearchBoxClear = () => {

  searchInputRef.current!.focus();

  hideDropdown();

  controller.clear();

  clearFilterSuggestions();

  instantProductsController.updateQuery('');

};



const onFocusSuggestion = (suggestion: Suggestion) => {

  instantProductsController.updateQuery(suggestion.rawValue);

  fetchFilterSuggestions(suggestion.rawValue);

};



const onSelectSuggestion = (suggestion: Suggestion) => {

  controller.selectSuggestion(suggestion.rawValue);

  hideDropdown();

};

```

<1> Update instant products with the new query to show relevant products in real time.

<2> When the user types in the search box, update filter suggestions with the new query.

<3> Clear instant products and filter suggestions when the user presses Escape.
### Render the dropdown

Build the dropdown that appears under the search input, displaying query suggestions, instant products, and filter suggestions side by side.

```tsx

const renderDropdown = () => { <1>

  return (

    <div className="SearchBoxDropdown row">

      {state.suggestions.length > 0 && (

        <div className="QuerySuggestion column small">

          <p>Query suggestions</p>

          <ul>

            {state.suggestions.map((suggestion) => (

              <li key={`${suggestion.rawValue}-suggestion`}>

                <button

                  type="button"

                  onMouseOver={() => onFocusSuggestion(suggestion)}

                  onFocus={() => onFocusSuggestion(suggestion)}

                  onClick={() => onSelectSuggestion(suggestion)}

                  // biome-ignore lint/security/noDangerouslySetInnerHtml: highlightedValue is sanitized by Headless

                  dangerouslySetInnerHTML={{

                    __html: suggestion.highlightedValue,

                  }}

                />

              </li>

            ))}

          </ul>

        </div>

      )}



      <div className="InstantProducts column small">

        <InstantProducts

          controller={instantProductsController}

          navigate={navigate}

        />

      </div>



      <div className="FilterSuggestions column small">

        <FilterSuggestionsGenerator

          controller={filterSuggestionsGeneratorController}

          onClickFilterSuggestion={( <2>

            fsController: FilterSuggestions | CategoryFilterSuggestions,

            value: RegularFacetSearchResult | CategoryFacetSearchResult

          ) => {

            hideDropdown();

            const parameters =

              fsController.type === 'hierarchical'

                ? fsController.getSearchParameters(

                    value as CategoryFacetSearchResult

                  )

                : fsController.getSearchParameters(

                    value as RegularFacetSearchResult

                  );

            navigate(`/search#${parameters}`); <3>

          }}

        />

      </div>

    </div>

  );

};

```

<1> Render the dropdown menu displaying query suggestions, instant products, and filter suggestions side by side.

<2> Handle filter suggestion clicks by hiding the dropdown and navigating to the search page with the selected filter applied.

<3> Navigate to the search page with the chosen filter value selected.
### Render the search box

Assemble the final component by combining the search input, clear and submit buttons, and the dropdown.

```tsx

return (

  <div className="Searchbox">

    <input

      aria-label="Enter query"

      className="SearchBoxInput"

      id="search-box"

      onChange={onSearchBoxInputChange} <1>

      onKeyDown={onSearchBoxInputKeyDown}

      ref={searchInputRef}

      value={state.value}

    />

    <button

      aria-label="Clear query"

      className="SearchBoxClear"

      disabled={

        state.isLoadingSuggestions || state.isLoading || state.value === ''

      }

      onClick={onClickSearchBoxClear}

      type="reset"

    >

      X

    </button>

    <button

      aria-label="Submit query"

      className="SearchBoxSubmit"

      disabled={state.isLoading}

      onClick={() => {

        controller.submit();

        hideDropdown();

      }}

      title="Submit query"

      type="submit"

    >

      Search

    </button>

    {isDropdownVisible && renderDropdown()}

  </div>

);

```

<1> Wire up event handlers that update both instant products and filter suggestions on every user interaction.
### Complete example

**Click to expand the complete `standalone-search-box.tsx` file.**
<details><summary>Details</summary>

```tsx
// src/components/standalone-search-box/standalone-search-box.tsx
import type {
  CategoryFacetSearchResult,
  CategoryFilterSuggestions,
  FilterSuggestions,
  FilterSuggestionsGenerator as HeadlessFilterSuggestionsGenerator,
  InstantProducts as HeadlessInstantProducts,
  StandaloneSearchBox as HeadlessStandaloneSearchBox,
  RegularFacetSearchResult,
  Suggestion,
} from '@coveo/headless/commerce';
import {useEffect, useRef, useState} from 'react';
import FilterSuggestionsGenerator from '../filter-suggestions/filter-suggestions-generator.js';
import InstantProducts from '../instant-products/instant-products.js';
import './standalone-search-box.css';

interface IStandaloneSearchBoxProps {
  navigate: (url: string) => void;
  controller: HeadlessStandaloneSearchBox;
  instantProductsController: HeadlessInstantProducts;
  filterSuggestionsGeneratorController: HeadlessFilterSuggestionsGenerator;
}
export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) {
  const {
    navigate,
    controller,
    instantProductsController,
    filterSuggestionsGeneratorController,
  } = props;
  const [state, setState] = useState(controller.state);
  const [isDropdownVisible, setIsDropdownVisible] = useState(false);
  const searchInputRef = useRef<HTMLInputElement>(null);

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

  const hideDropdown = () => setIsDropdownVisible(false);
  const showDropdown = () => setIsDropdownVisible(true);
  const fetchFilterSuggestions = (value: string) => { <1>
    for (const filterSuggestions of filterSuggestionsGeneratorController.filterSuggestions) {
      filterSuggestions.updateQuery(value);
    }
  };

  const clearFilterSuggestions = () => { <2>
    for (const filterSuggestions of filterSuggestionsGeneratorController.filterSuggestions) {
      filterSuggestions.clear();
    }
  };
  const onSearchBoxInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.value === '') {
      hideDropdown();
      controller.clear();
      return;
    }

    controller.updateText(e.target.value);
    controller.showSuggestions();
    instantProductsController.updateQuery(e.target.value); <3>
    fetchFilterSuggestions(e.target.value); <4>
    showDropdown();
  };

  const onSearchBoxInputKeyDown = (
    e: React.KeyboardEvent<HTMLInputElement>
  ) => {
    switch (e.key) {
      case 'Escape':
        if (isDropdownVisible) {
          hideDropdown();
          break;
        }
        if (state.value !== '') {
          controller.clear();
          clearFilterSuggestions(); <5>
          instantProductsController.updateQuery('');
          break;
        }
        break;
      case 'Enter':
        hideDropdown();
        controller.submit();
        break;
    }
  };

  const onClickSearchBoxClear = () => {
    searchInputRef.current!.focus();
    hideDropdown();
    controller.clear();
    clearFilterSuggestions();
    instantProductsController.updateQuery('');
  };

  const onFocusSuggestion = (suggestion: Suggestion) => {
    instantProductsController.updateQuery(suggestion.rawValue);
    fetchFilterSuggestions(suggestion.rawValue);
  };

  const onSelectSuggestion = (suggestion: Suggestion) => {
    controller.selectSuggestion(suggestion.rawValue);
    hideDropdown();
  };
  const renderDropdown = () => { <6>
    return (
      <div className="SearchBoxDropdown row">
        {state.suggestions.length > 0 && (
          <div className="QuerySuggestion column small">
            <p>Query suggestions</p>
            <ul>
              {state.suggestions.map((suggestion) => (
                <li key={`${suggestion.rawValue}-suggestion`}>
                  <button
                    type="button"
                    onMouseOver={() => onFocusSuggestion(suggestion)}
                    onFocus={() => onFocusSuggestion(suggestion)}
                    onClick={() => onSelectSuggestion(suggestion)}
                    // biome-ignore lint/security/noDangerouslySetInnerHtml: highlightedValue is sanitized by Headless
                    dangerouslySetInnerHTML={{
                      __html: suggestion.highlightedValue,
                    }}
                  />
                </li>
              ))}
            </ul>
          </div>
        )}

        <div className="InstantProducts column small">
          <InstantProducts
            controller={instantProductsController}
            navigate={navigate}
          />
        </div>

        <div className="FilterSuggestions column small">
          <FilterSuggestionsGenerator
            controller={filterSuggestionsGeneratorController}
            onClickFilterSuggestion={( <7>
              fsController: FilterSuggestions | CategoryFilterSuggestions,
              value: RegularFacetSearchResult | CategoryFacetSearchResult
            ) => {
              hideDropdown();
              const parameters =
                fsController.type === 'hierarchical'
                  ? fsController.getSearchParameters(
                      value as CategoryFacetSearchResult
                    )
                  : fsController.getSearchParameters(
                      value as RegularFacetSearchResult
                    );
              navigate(`/search#${parameters}`); <8>
            }}
          />
        </div>
      </div>
    );
  };
  return (
    <div className="Searchbox">
      <input
        aria-label="Enter query"
        className="SearchBoxInput"
        id="search-box"
        onChange={onSearchBoxInputChange} <9>
        onKeyDown={onSearchBoxInputKeyDown}
        ref={searchInputRef}
        value={state.value}
      />
      <button
        aria-label="Clear query"
        className="SearchBoxClear"
        disabled={
          state.isLoadingSuggestions || state.isLoading || state.value === ''
        }
        onClick={onClickSearchBoxClear}
        type="reset"
      >
        X
      </button>
      <button
        aria-label="Submit query"
        className="SearchBoxSubmit"
        disabled={state.isLoading}
        onClick={() => {
          controller.submit();
          hideDropdown();
        }}
        title="Submit query"
        type="submit"
      >
        Search
      </button>
      {isDropdownVisible && renderDropdown()}
    </div>
  );
}
```

<1> Define a function to fetch filter suggestions by updating the query on each `FilterSuggestions` sub-controller.
<2> Define a function to clear all filter suggestions.
<3> Update instant products with the new query to show relevant products in real time.
<4> When the user types in the search box, update filter suggestions with the new query.
<5> Clear instant products and filter suggestions when the user presses Escape.
<6> Render the dropdown menu displaying query suggestions, instant products, and filter suggestions side by side.
<7> Handle filter suggestion clicks by hiding the dropdown and navigating to the search page with the selected filter applied.
<8> Navigate to the search page with the chosen filter value selected.
<9> Wire up event handlers that update both instant products and filter suggestions on every user interaction.

</details>
## What's next

With instant products and filter suggestions integrated into your search box, you have a complete search-as-you-type experience.
To continue building your commerce interface, see [Displaying products](https://docs.coveo.com/en/o8ce0239.md) to render full product listings on your search and product listing pages.