Using Search Hooks | Yext Hitchhikers Platform
What You’ll Learn
- How to use the
SearchHeadless
state directly with theuseSearchState
hook - How to trigger
SearchHeadless
actions with theuseSearchActions
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 {
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.
module-2
branch in the
pages-starter-search-ui-react repo
.