Enhancing Your KNN Search UI with Faceting: A Follow-Up Guide
Build Hybrid Search with ReactiveSearch UI Kit and Elasticsearch 8.17

CEO @reactivesearch, search engine dx
Search for a command to run...
Build Hybrid Search with ReactiveSearch UI Kit and Elasticsearch 8.17

CEO @reactivesearch, search engine dx
No comments yet. Be the first to comment.
Supercharged search for Elasticsearch and OpenSearch

Build Semantic Search UIs with Faceting and Production-Ready Performance in One Unified API

We will start by creating vector embeddings and indexing data, then create a search endpoint, and finally build a search UI.

We will start by creating vector embeddings and indexing data, then create a search endpoint, and finally build a search UI.

In our previous post, we explored how vector (KNN) search lets you search for what you mean rather than what you type. In this post, we’re taking it one step further by integrating faceting capabilities into our search UI. By combining semantic search with faceting, users can filter results (for example, by industry) and get even more precise matches.
Note: With ReactiveSearch UI, you can now seamlessly integrate vector search capabilities with most of your UI components. This hybrid search approach merges vector search with traditional keyword search techniques to deliver contextual, precise results across all of your data in any modality, with less effort.
Try out the live demo: Knn search with faceting demo
Under the hood, ReactiveSearch leverages the Approximate kNN query DSL in Elasticsearch and OpenSearch, applying faceting during the kNN search phase to deliver precise, context-aware results.
Before diving in, ensure that you:
@appbaseio/reactivesearch (for building the search UI)@emotion/styled (for styling components)react-icons (for adding iconography)Note:
dense_vector in your Elasticsearch index mapping. If you're using the index utility provided by Elasticsearch, it will configure this for you.We start by wrapping our UI with ReactiveBase to connect to the search cluster and add a SearchBox for capturing semantic queries.
<ReactiveBase
app="yc-companies-dataset"
url="https://appbase-demo-ansible-abxiydt-arc.searchbase.io"
credentials="a03a1cb71321:75b6603d-9456-4a5a-af6b-a487b309eb61"
transformRequest={(request) => {
// Custom query transformation logic
}}
>
<Container>
<h2>K-Nearest Neighbors Search with Facets</h2>
<SearchBox
componentId="search"
dataField={["name", "one_liner"]}
placeholder="Semantic search for startup companies"
autosuggest={false}
style={{ marginBottom: "1rem" }}
URLParams
/>
{/* Additional layout components go here */}
</Container>
</ReactiveBase>
Next, add a MultiList component that enables filtering by industries. It is configured to react to the search input.
<FacetContainer>
<MultiList
componentId="industries"
dataField="industries.keyword"
title="Industries"
placeholder="Filter by industries"
showSearch={false}
react={{ and: ["search"] }}
style={{ marginBottom: "1rem" }}
/>
</FacetContainer>
The ReactiveList component renders our search results. Each result displays company details like logo, name, description, team size, stage, industry tags, and a website link.
<ResultsContainer>
<ReactiveList
componentId="results"
vectorDataField="vector_data"
dataField="_score"
size={20}
pagination={false}
react={{ and: ["search", "industries"] }}
includeFields={[
"name",
"one_liner",
"long_description",
"team_size",
"stage",
"industries",
"website",
"small_logo_thumb_url",
]}
render={({ data }) => (
<>
{data.map((item) => {
const company = item._source || item;
return (
<ResultItem key={company._id}>
<Logo
src={company.small_logo_thumb_url}
alt={`${company.name} logo`}
/>
<Info>
<CompanyName>{company.name}</CompanyName>
<OneLiner>
{company.one_liner || company.long_description}
</OneLiner>
<Meta>
<span>
<FaUsers /> {company.team_size}
</span>
<span>
<FaBuilding /> {company.stage}
</span>
</Meta>
<Tags>
{company.industries &&
company.industries.map((ind) => (
<Tag key={ind}>{ind}</Tag>
))}
</Tags>
<Link
href={company.website}
target="_blank"
rel="noopener noreferrer"
>
Visit website
</Link>
</Info>
</ResultItem>
);
})}
</>
)}
renderNoResults={() => <div>No results found</div>}
/>
</ResultsContainer>
The transformRequest function customizes the search query before sending it to Elasticsearch. It dynamically adjusts the payload based on whether a semantic query or industry filter (or both) are applied.
transformRequest={(request) => {
const body = JSON.parse(request.body);
const searchQuery = body.query.find((q) => q.id === "search");
const industriesQuery = body.query.find((q) => q.id === "industries");
const resultsQueryIndex = body.query.findIndex((q) => q.id === "results");
const resultsQuery = body.query.find((q) => q.id === "results");
if (
(searchQuery?.value && searchQuery.value !== "") ||
(industriesQuery?.value && industriesQuery.value.length > 0)
) {
if (resultsQuery) {
resultsQuery.candidates = resultsQuery.size;
if (searchQuery.value) {
resultsQuery.value = searchQuery.value;
resultsQuery.react = { and: ["industries"] };
} else if (industriesQuery.value) {
resultsQuery.value = industriesQuery.value.join(" ");
}
if (resultsQuery.dataField) {
delete resultsQuery.dataField;
}
body.query[resultsQueryIndex] = resultsQuery;
}
} else {
body.query.splice(resultsQueryIndex, 1);
}
return {
...request,
body: JSON.stringify(body),
};
}}
Each component works together to create a hybrid search experience that combines vector search with traditional faceting.
Check out the live demo here: KNN with Facets Demo. You can browse the code for the demo over here: https://github.com/awesome-reactivesearch/opensource-demos/blob/main/src/pages/KNN/knn-with-faceting.jsx.
In this follow-up guide, we enhanced our original vector search UI by incorporating faceting. This approach allows users to refine search results effectively, making the overall search experience more precise and user-friendly. Leveraging the ReactiveSearch UI kit, you can easily combine the power of hybrid search techniques with traditional filters to deliver an unmatched search experience. Stay tuned for more advanced search UI enhancements using ReactiveSearch!