Step 2: Write a renderEntityPreviews Function

The entities returned by universal results are rendered using a custom renderEntityPreviews function. This function has 3 parameters:

  • autocompleteLoading → boolean field that is true when waiting for the Universal Results API to return
  • verticalKeyToResults → an object that maps each vertical passed to the function to a list of results
  • onSubmit → the same function that is triggered by a search from the <SearchBar />

renderEntityPreviews returns a JSX.Element or null depending if there are results to preview or not.

Since the SearchBar has no way of knowing which type that each vertical will contain, this example casts the products vertical to Product[] . This is acceptable type casting because it’s known what type that the products vertical contains.

The element returned by this function should be container div with a series of DropdownItem components that contain each entity preview.

The example returns a container in where the products vertical results are laid out in a grid format:

// src/App.tsx

import {
  provideHeadless,
  VerticalResults as VerticalResultsData,
} from "@yext/search-headless-react";
import {
  SearchBar,
  UniversalResults,
  FocusedItemData,
  DropdownItem,
} from "@yext/search-ui-react";
import { searchConfig } from "./config/searchConfig";
import { Product } from "./types/products";
import classnames from "classnames";

const App = (): JSX.Element => {
  const entityPreviewSearcher = provideHeadless({
    ...searchConfig,
    headlessId: "entity-preview-searcher",
  });

  const renderProductPreview = (product: Product): JSX.Element => {
    // getting the smallest thumbnail image from the primaryPhoto field
    const numThumbnails = product.primaryPhoto?.image.thumbnails?.length || 0;
    const productThumbnail =
      product.primaryPhoto?.image.thumbnails?.[numThumbnails - 1];

    return (
      <div className="flex flex-col items-center cursor-pointer hover:bg-gray-100 ">
        {productThumbnail && (
          <img className="w-32" src={productThumbnail.url} />
        )}
        <div className="font-semibold pl-3">{product.name}</div>
      </div>
    );
  };

  const renderEntityPreviews = (
    autocompleteLoading: boolean,
    verticalKeyToResults: Record<string, VerticalResultsData>,
    dropdownItemProps: {
      onClick: (
        value: string,
        _index: number,
        itemData?: FocusedItemData
      ) => void;
      ariaLabel: (value: string) => string;
    }
  ): JSX.Element | null => {
    const productResults = verticalKeyToResults["products"]?.results.map(
      (result) => result.rawData
    ) as unknown as Product[];

    return productResults ? (
      <div
        // laying out the product previews in a grid
        className={classnames("grid grid-cols-4 px-8", {
          // fading the results if they're loading
          "opacity-50": autocompleteLoading,
        })}
      >
        {productResults.map((result, i) => (
          // DropdownItem is impored from @yext/search-ui-react
          <DropdownItem
            key={result.id}
            value={result.name}
            // when an item is clicked, it will change the URL
            onClick={() => history.pushState(null, "", `/product/${result.id}`)}
            ariaLabel={dropdownItemProps.ariaLabel}
          >
            {renderProductPreview(result)}
          </DropdownItem>
        ))}
      </div>
    ) : null;
  };

  return (
    <div className="flex justify-center px-4 py-6">
      <div className="w-full max-w-5xl">
        <SearchBar />
        <UniversalResults
          verticalConfigMap={{
            faqs: {},
            locations: {},
            products: {},
          }}
        />
      </div>
    </div>
  );
};

export default App;
light bulb
Note
Wondering where the Product type import came from? Check out this page on search result typing and how to generate types based on your search configuration.

You’ll notice that this example changes the URL rather than using the onSubmit function to trigger a search when the result is clicked. You could set up routing in your application to route to a product specific page.