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 returnverticalKeyToResults
→ an object that maps each vertical passed to the function to a list of resultsonSubmit
→ 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;
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.