Using Search Hooks | Yext Hitchhikers Platform

What You’ll Learn

  • How to use the SearchHeadless state directly with the useSearchState hook
  • How to trigger SearchHeadless actions with the useSearchActions hook

Overview

Under the hood, Search UI components use SearchHeadless hooks to read the search state and trigger actions based on events in the UI. To use the hooks, every component needs to be wrapped in a SearchHeadlessProvider.

When you add Search UI React to your project, you are able import these same hooks directly into custom components. In this unit, you’re going to use the hooks to display a message when there are no results for a query and trigger a default job search when the search page renders.

1. Wrap Your Search Experience

Since the Search UI components we are using are already wrapped in a SearchHeadlessProvider, they can trigger actions and read from state using Search Headless hooks. However, since we want to invoke the hooks directly, we need to refactor our code to wrap a new component in our provider.

Create a new file in src/components called JobSearch.tsx. Inside, you’re going to paste the following code which is extracted from search.tsx.

// src/components/JobSearch.tsx

import * as React from "react";
import {
  SearchBar,
  SpellCheck,
  ResultsCount,
  Facets,
  VerticalResults,
  Pagination,
} from "@yext/search-ui-react";
import JobCard from "./JobCard";

const JobSearch = (): JSX.Element => {
  return (
    <div className="px-4 py-8">
      <div className="mx-auto flex max-w-5xl flex-col">
        <h1 className="pb-4 text-center text-3xl font-bold text-red-700">
          Turtlehead Tacos Careers
        </h1>
        <SearchBar placeholder="Search job title, department, or employment type" />
        <SpellCheck
          customCssClasses={{
            link: "text-red-700 underline",
          }}
        />
        <ResultsCount />
        <div className="flex">
          <div className="mr-5 w-56 shrink-0">
            <div className="flex flex-col rounded border bg-zinc-100 p-4 shadow-sm">
              <Facets
                customCssClasses={{
                  optionInput: "text-red-700 focus:ring-red-700",
                  optionLabel: "text-stone-900",
                }}
              />
            </div>
          </div>
          <VerticalResults<Job>
            CardComponent={JobCard}
            displayAllOnNoResults={false}
          />
        </div>
      </div>
      <Pagination
        customCssClasses={{
          icon: "text-stone-900",
          label: "text-stone-900",
          selectedLabel: "text-red-700 border-red-700 bg-red-100",
        }}
      />
    </div>
  );
};

export default JobSearch;

Then, update search.tsx to render JobSearch.

// src/templates/search.tsx

// other imports...

import JobSearch from "../components/JobSearch";

// other code...

const Search: Template<TemplateRenderProps> = () => {
  const searcher = provideHeadless(headlessConfig);

  return (
    <SearchHeadlessProvider searcher={searcher}>
      {/* new code starts here... */}
      <JobSearch />
      {/* ...and ends here */}
    </SearchHeadlessProvider>
  );
};

export default Search;

You can also remove the unused @yext/search-ui-react imports from search.tsx. Run another search on “jobs” to ensure that your search experience still looks the same.

2. “No Results” Message

Right now, if you use a search query that returns no results, you’ll see an empty area under the search bar. We’re going to change this to display a message if there are no results for a given search query.

First, grab the most recent search query and vertical results count from the SearchHeadless state with the useSearchState hook:

// src/components/JobSearch.tsx

// other imports...

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

const JobSearch = (): JSX.Element => {
  const mostRecentSearch = useSearchState(
    (state) => state.query.mostRecentSearch
  )
  const resultsCount =
    useSearchState((state) => state.vertical.resultsCount) ?? 0;

  // other code...
};

Then, conditionally render either the vertical results or a message based on resultsCount and mostRecentSearch:

// src/components/JobSearch.tsx

// imports...

const JobSearch = (): JSX.Element => {
  const mostRecentSearch = useSearchState(
    (state) => state.query.mostRecentSearch
  );
  const resultsCount =
    useSearchState((state) => state.vertical.resultsCount) ?? 0;

  return (
    <div className="px-4 py-8">
      <div className="mx-auto flex max-w-5xl flex-col">
        <h1 className="pb-4 text-center text-3xl font-bold text-red-700">
          Turtlehead Tacos Careers
        </h1>
        <SearchBar placeholder="Search job title, department, or employment type" />
        <SpellCheck
          customCssClasses={{
            link: "text-red-700 underline",
          }}
        />
        <ResultsCount />
        {/* new code starts here... */}
        {resultsCount > 0 && (
          <div className="flex">
            <div className="mr-5 w-56 shrink-0">
              <div className="flex flex-col rounded border bg-zinc-100 p-4 shadow-sm">
                <Facets
                  customCssClasses={{
                    optionInput: "text-red-700 focus:ring-red-700",
                    optionLabel: "text-stone-900",
                  }}
                />
              </div>
            </div>
            <VerticalResults<Job>
              CardComponent={JobCard}
              displayAllOnNoResults={false}
            />
          </div>
        )}
        {mostRecentSearch && resultsCount === 0 && (
          <div>
            <p>
              The search
              <span className="mx-1 font-semibold">{mostRecentSearch}</span>
              did not match any jobs.
            </p>
          </div>
        )}
        {/* ...and ends here */}
      </div>
      <Pagination
        customCssClasses={{
          icon: "text-stone-900",
          label: "text-stone-900",
          selectedLabel: "text-red-700 border-red-700 bg-red-100",
        }}
      />
    </div>
  );
};

export default JobSearch;

Search for “developer”. There should be no results shown along with an informative message.

3. Trigger Search on Render

We want users to see a selection of jobs as soon as they arrive on the Job Search page.

Import useSearchActions and wrap it in a useEffect to trigger a blank vertical query as soon as the search experience renders:

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

const JobSearch = (): JSX.Element => {
  const mostRecentSearch = useSearchState(
    (state) => state.query.mostRecentSearch
  );
  const resultsCount =
    useSearchState((state) => state.vertical.resultsCount) ?? 0;

  const searchActions = useSearchActions();

  React.useEffect(() => {
    searchActions.executeVerticalQuery();
  }, []);

  // ...other search experience code
};

At this point, you’re handling cases where there are no results both before and after the user makes their first search.

Try refreshing your page. You should see Job results and facets appear.

light bulb
Note
If you want to confirm what your code should look like at this point, take a look at the module-2 branch in the pages-starter-search-ui-react repo .
unit Quiz
+20 points
Daily Quiz Streak Daily Quiz Streak: 0
Quiz Accuracy Streak Quiz Accuracy Streak: 0
    Error Success Question 1 of 2

    True or False: You can use useSearchActions() outside of a SearchHeadlessProvider component.

    Error Success Question 2 of 2

    What do the Search UI components use useSearchState() for?

    Wahoo - you did it! 🙌

    You've already completed this quiz, so you can't earn more points.You completed this quiz in 1 attempt and earned 0 points! Feel free to review your answers and move on when you're ready.
1st attempt
0 incorrect
Feedback