How to Build Great React Search Experiences Quickly

Jason Stoltzfus

Building great search requires two sophisticated parts: (1) the search engine, which provides APIs to power search and (2) the search library, which paints the search experience.

The plan: index documents which represent the best video games of all time into a search engine, then design and optimize a search experience to search through them.

npm install -g create-react-app
create-react-app video-game-search --use-npm
cd video-game-search
npm install — save @elastic/react-search-ui @elastic/search-ui-app-search-connector
npm start
// Step #1, import Statements
import React from "react";
import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";
import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";
import { Layout } from "@elastic/react-search-ui-views";
import "@elastic/react-search-ui-views/lib/styles/styles.css";
// Step #2, The Connector
const connector = new AppSearchAPIConnector({
searchKey: "[YOUR_SEARCH_KEY]",
engineName: "video-games",
hostIdentifier: "[YOUR_HOST_IDENTIFIER]"
});
// Step #3: Configuration Options
const configurationOptions = {
apiConnector: connector
// Let's fill this in together.
};
// Step #4, SearchProvider: The Finishing Touches.
export default function App() {
return (
<SearchProvider config={configurationOptions}>
<div className="App">
<Layout
// Let's fill this in together.
/>
</div>
</SearchProvider>
);
}
import React from "react";import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";import { Layout } from "@elastic/react-search-ui-views";import "@elastic/react-search-ui-views/lib/styles/styles.css";
import "@elastic/react-search-ui-views/lib/styles/styles.css";
const connector = new AppSearchAPIConnector({
searchKey: "[YOUR_SEARCH_KEY]",
engineName: "video-games",
hostIdentifier: "[YOUR_HOST_IDENTIFIER]"
});
{
"id": "final-fantasy-vii-ps-1997",
"name": "Final Fantasy VII",
"year": 1997,
"platform": "PS",
"genre": "Role-Playing",
"publisher": "Sony Computer Entertainment",
"global_sales": 9.72,
"critic_score": 92,
"user_score": 9,
"developer": "SquareSoft",
"image_url": "https://r.hswstatic.com/w_907/gif/finalfantasyvii-MAIN.jpg"
}
const configurationOptions = {
apiConnector: connector,
searchQuery: {
search_fields: {
// 1. Search by name of video game.
name: {}
},
// 2. Results: name of the video game, its genre, publisher, scores, and platform.
result_fields: {
name: {
// A snippet means that matching search terms will be highlighted via <em> tags.
snippet: {
size: 75, // Limit the snippet to 75 characters.
fallback: true // Fallback to a "raw" result.
}
},
genre: {
snippet: {
size: 50,
fallback: true
}
},
publisher: {
snippet: {
size: 50,
fallback: true
}
},
critic_score: {
// Scores are numeric, so we won't attempt to snippet these, we'll just use the raw
// value.
raw: {}
},
user_score: {
raw: {}
},
platform: {
snippet: {
size: 50,
fallback: true
}
},
image_url: {
raw: {}
}
},
// 3. Facet by scores, genre, publisher, and platform, which we'll use to build filters later.
facets: {
user_score: {
type: "range",
ranges: [
{ from: 0, to: 5, name: "Not good" },
{ from: 5, to: 7, name: "Not bad" },
{ from: 7, to: 9, name: "Pretty good" },
{ from: 9, to: 10, name: "Must play!" }
]
},
critic_score: {
type: "range",
ranges: [
{ from: 0, to: 50, name: "Not good" },
{ from: 50, to: 70, name: "Not bad" },
{ from: 70, to: 90, name: "Pretty good" },
{ from: 90, to: 100, name: "Must play!" }
]
},
genre: { type: "value", size: 100 },
publisher: { type: "value", size: 100 },
platform: { type: "value", size: 100 }
}
}
};
export default function App() {
return (
<SearchProvider config={configurationOptions}>
<div className="App">
<Layout
header={<SearchBox />}
// titleField is the most prominent field within a result: the result header.
bodyContent={<Results titleField="name" urlField="image_url" />}
/>
</div>
</SearchProvider>
);
}

Synonyms

Curations

Relevance Tuning

{ 
"name":"Magical Quest",
"description": "A dangerous journey through caves and such."
},
{
"name":"Dangerous Quest",
"description": "A magical journey filled with magical magic. Highly magic."
}
import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";
import {
PagingInfo,
ResultsPerPage,
Paging,
Facet,
SearchProvider,
Results,
SearchBox,
Sorting
} from "@elastic/react-search-ui";
<Layout
header={<SearchBox />}
bodyContent={<Results titleField="name" urlField="image_url" />}
sideContent={
<div>
<Sorting
label={"Sort by"}
sortOptions={[
{
name: "Relevance",
value: "",
direction: ""
},
{
name: "Name",
value: "name",
direction: "asc"
}
]}
/>
<Facet field="user_score" label="User Score" />
<Facet field="critic_score" label="Critic Score" />
<Facet field="genre" label="Genre" />
<Facet field="publisher" label="Publisher" isFilterable={true} />
<Facet field="platform" label="Platform" />
</div>
}
bodyHeader={
<>
<PagingInfo />
<ResultsPerPage />
</>
}
bodyFooter={<Paging />}
/>;
const configurationOptions = {
// ...
autocompleteQuery: {
suggestions: {
types: {
documents: {
// Which fields to search for suggestions.
fields: ["name"]
},
// How many suggestions appear.
size: 5
}
}
}
// ...
};
// ...
<Layout
// ...
header={<SearchBox autocompleteSuggestions={true} />}
/>
// ...