Integrate Autocomplete with React InstantSearch
When you think of search experiences on sites like Amazon (ecommerce) or YouTube (media), you may notice that both sites use an autocomplete experience. It’s the autocomplete, and not just a search input, that powers the search page.
If you have an existing React InstantSearch implementation, you can create a similar experience by adding Autocomplete to your React InstantSearch application. Adding Autocomplete to an existing React InstantSearch implementation lets you enhance the search experience and create a richer, more contextual search. You can use context from the current user and how they interacted with your site, save their recent searches, provide suggested queries, and more. This autocomplete can work as a rich search box in a search page, and a portable all-in-one search experience anywhere else on your site.
This guide shows you how to integrate Autocomplete with React InstantSearch on your site.
This guide starts from a brand new React InstantSearch application, but you can adapt it to integrate Autocomplete in your existing implementation.
Creating a search page with React InstantSearch
First, begin with some boilerplate for the InstantSearch implementation. The easiest way to achieve that is to use create-instantsearch-app
, a command-line utility that helps get your app started quickly.
$
$
$
$
$
$
$
npx create-instantsearch-app autocomplete-react-instantsearch \
--template "React InstantSearch" \
--app-id "latency" \
--api-key "6be0576ff61c053d5f9a3225e2a90f76" \
--index-name instant_search \
--attributes-to-display name,description \
--attributes-for-faceting categories
The template uses a two-column layout with categories on the left and a search box, hits, and a pagination widget on the right.
Next, set up InstantSearch to enable routing by using the default router, which you want to reuse in your Autocomplete integration. This lets InstantSearch understand query parameters from the URL to derive its state.
$
npm install --save qs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import {
connectSearchBox,
...
} from 'react-instantsearch-dom';
import qs from 'qs';
const VirtualSearchBox = connectSearchBox(() => null);
function createURL(searchState) {
return qs.stringify(searchState, { addQueryPrefix: true });
}
function searchStateToUrl({ location }, searchState) {
if (Object.keys(searchState).length === 0) {
return '';
}
return `${location.pathname}${createURL(searchState)}`;
}
function urlToSearchState({ search }) {
return qs.parse(search.slice(1));
}
export function App() {
const [searchState, setSearchState] = React.useState(() =>
urlToSearchState(window.location)
);
const timerRef = React.useRef(null);
React.useEffect(() => {
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
window.history.pushState(
searchState,
null,
searchStateToUrl({ location: window.location }, searchState)
);
}, 400);
}, [searchState]);
return <InstantSearch
searchClient={searchClient}
indexName="instant_search"
searchState={searchState}
onSearchStateChange={setSearchState}
createURL={createURL}
>
{/* A virtual search box is required for InstantSearch to understand the `query` search state property */}
<VirtualSearchBox />
{ /* ... */ }
</InstantSearch>
}
You should now have a working React InstantSearch application with routing.
Using Autocomplete as a search box
React InstantSearch ships with a SearchBox
component, but it doesn’t offer autocomplete features like those seen on YouTube and Amazon. Instead, you can replace the SearchBox
with Algolia’s Autocomplete.
With React, you can store all the logic of Autocomplete in a single file, and expose it as a component. Start by creating a file called Autocomplete.jsx
.
$
npm install --save @algolia/autocomplete-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// file: Autocomplete.jsx
import { autocomplete } from '@algolia/autocomplete-js';
import React, { createElement, Fragment, useEffect, useRef } from 'react';
import { render } from 'react-dom';
export function Autocomplete(props) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) {
return undefined;
}
const search = autocomplete({
container: containerRef.current,
renderer: { createElement, Fragment, render },
...props,
});
return () => {
search.destroy();
};
}, [props]);
return <div ref={containerRef} />;
}
Make sure to load the autocomplete styles:
1
2
<!-- in index.html -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@algolia/autocomplete-theme-classic">
You can now add this component into your app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { Autocomplete } from './Autocomplete';
export function App() {
// ...
const onSubmit = React.useCallback(({ state }) => {
setSearchState((searchState) => ({
...searchState,
query: state.query,
}));
}, []);
const onReset = React.useCallback(() => {
setSearchState((searchState) => ({
...searchState,
query: '',
}));
}, []);
const plugins = React.useMemo(() => {
return []; // add more plugins here
}, []);
return <InstantSearch
searchClient={searchClient}
indexName="instant_search"
searchState={searchState}
onSearchStateChange={setSearchState}
createURL={createURL}
>
{...}
<Autocomplete
placeholder="Search"
detachedMediaQuery="none"
initialState={{
query: searchState.query,
}}
openOnFocus={true}
onSubmit={onSubmit}
onReset={onReset}
plugins={plugins}
/>
</InstantSearch>
}
Adding recent searches
When you search on YouTube or Google and come back to the search box later on, the autocomplete displays your recent searches. This pattern lets users quickly access content by using the same path they took to find it in the first place.
Add recent searches to Autocomplete with the @algolia/autocomplete-plugin-recent-searches
package. It exposes a createLocalStorageRecentSearchesPlugin
function to let you create a recent searches plugin.
$
npm install --save @algolia/autocomplete-plugin-recent-searches
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
// ...
const plugins = React.useMemo(() => {
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
key: 'search',
limit: 3,
transformSource({ source }) {
return {
...source,
onSelect({ item }) {
setSearchState((searchState) => ({
...searchState,
query: item.label,
}));
},
};
},
});
return [
recentSearchesPlugin,
];
}, []);
Since the recentSearchesPlugin
reads from localStorage
, you can’t see any recent searches until you perform at least one query. To submit a search, make sure to press Enter on the query. Once you do, you’ll see it appear as a recent search.
Adding Query Suggestions
The most typical pattern you can see on every autocomplete is suggestions. They’re predictions of queries that match what the user is typing and are guaranteed to return results. For example, when typing “how to” in Google, the search engine suggests matching suggestions for the user to complete their query. It’s beneficial on mobile devices, where typing is more demanding than a physical keyboard.
Autocomplete lets you add Query Suggestions with the @algolia/autocomplete-plugin-query-suggestions
package. It exposes a createQuerySuggestionsPlugin
function to let you create a Query Suggestions plugin.
This plugin requires a Query Suggestions index.
$
npm install --save @algolia/autocomplete-plugin-query-suggestions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';
// ...
const plugins = React.useMemo(() => {
//...
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
searchClient,
indexName: 'instant_search_demo_query_suggestions',
getSearchParams() {
// This creates a shared `hitsPerPage` value once the duplicates
// between recent searches and Query Suggestions are removed.
return recentSearchesPlugin.data.getAlgoliaSearchParams({
hitsPerPage: 6,
});
},
transformSource({ source }) {
return {
...source,
onSelect({ item }) {
setSearchState((searchState) => ({
...searchState,
query: item.query,
}));
},
};
},
});
return [
recentSearchesPlugin,
querySuggestionsPlugin,
];
}, []);
Supporting categories in Query Suggestions
A key feature of Autocomplete is to pre-configure your InstantSearch page. The Query Suggestions plugin supports categories that you can leverage to refine the query and the category in a single interaction. This pattern brings users to the right category without interacting with a widget, only with the autocomplete.
First, you need to refine on categories, for instance with a HierarchicalMenu
widget:
1
2
3
4
5
6
7
8
9
10
11
12
const INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES = [
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
];
export function App() {
// ...
return <InstantSearch /* ... */>
{/* ... */}
<HierarchicalMenu attributes={INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES} />
</InstantSearch>
}
Then, you can update the plugins to forward the category to the InstantSearch state.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const plugins = React.useMemo(() => {
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
// ...
transformSource({ source }) {
return {
...source,
onSelect({ item }) {
setSearchState((searchState) => ({
...searchState,
query: item.label,
hierarchicalMenu: {
[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]]:
item.category || '',
},
}));
},
};
},
});
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
// ...
categoryAttribute: [
'instant_search',
'facets',
'exact_matches',
INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0],
],
transformSource({ source }) {
return {
...source,
onSelect({ item }) {
setSearchState((searchState) => ({
...searchState,
query: item.query,
hierarchicalMenu: {
[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]]:
item.__autocomplete_qsCategory || '',
},
}));
},
};
},
});
return [
recentSearchesPlugin,
querySuggestionsPlugin,
];
}, []);
Finally, you can update the onReset
function to also reset the InstantSearch category.
1
2
3
4
5
6
7
8
9
10
11
12
export function App() {
// ...
const onReset = React.useCallback(() => {
setSearchState((searchState) => ({
...searchState,
query: '',
hierarchicalMenu: {
[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]]: '',
},
}));
});
}
Adding contextual Query Suggestions
To enrich the Autocomplete experience, you can provide suggestions to the currently active InstantSearch category as well as other categories. This pattern enables you to:
- reduce the scope of the search to the current category. For instance, like a physical department store.
- broaden the suggestions that you get out of the current category.
First, make sure to set your category attribute as a facet in your Query Suggestions index. In this demo, the attribute to facet is instant_search.facets.exact_matches.hierarchicalCategories.lvl0.value
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// ...
const currentCategory = useMemo(
() =>
searchState?.hierarchicalMenu?.[
INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]
] || '',
[searchState]
);
const plugins = React.useMemo(() => {
// ...
const querySuggestionsInCategoryPlugin = createQuerySuggestionsPlugin({
searchClient,
indexName: 'instant_search_demo_query_suggestions',
getSearchParams() {
return recentSearchesPlugin.data.getAlgoliaSearchParams({
hitsPerPage: 3,
facetFilters: [
`instant_search.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]}.value:${currentCategory}`,
],
});
},
transformSource({ source }) {
return {
...source,
sourceId: 'querySuggestionsInCategoryPlugin',
onSelect({ item }) {
setSearchState((searchState) => ({
...searchState,
query: item.query,
hierarchicalMenu: {
[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]]:
item.__autocomplete_qsCategory || '',
},
}));
},
};
},
getItems(params) {
if (currentCategory.length === 0) {
return [];
}
return source.getItems(params);
},
templates: {
...source.templates,
header({ items }) {
if (items.length === 0) {
return <Fragment />;
}
return (
<Fragment>
<span className="aa-SourceHeaderTitle">
In {currentCategory}
</span>
<span className="aa-SourceHeaderLine" />
</Fragment>
);
},
},
});
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
// ...
getSearchParams() {
if (currentCategory.length === 0) {
return recentSearchesPlugin.data.getAlgoliaSearchParams({
hitsPerPage: 6,
});
}
return recentSearchesPlugin.data.getAlgoliaSearchParams({
hitsPerPage: 3,
facetFilters: [
`instant_search.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]}.value:-${currentCategory}`,
],
});
},
transformSource({ source }) {
return {
...source,
onSelect({ item }) {
setSearchState((searchState) => ({
...searchState,
query: item.query,
hierarchicalMenu: {
[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]]:
item.__autocomplete_qsCategory || '',
},
}));
},
getItems(params) {
if (!params.state.query) {
return [];
}
return source.getItems(params);
},
templates: {
...source.templates,
header({ items }) {
if (currentCategory.length === 0 || items.length === 0) {
return <Fragment />;
}
return (
<Fragment>
<span className="aa-SourceHeaderTitle">
In other categories
</span>
<span className="aa-SourceHeaderLine" />
</Fragment>
);
},
},
};
},
});
return [
recentSearchesPlugin,
querySuggestionsInCategoryPlugin,
querySuggestionsPlugin,
];
}, [currentCategory]);
Next steps
Autocomplete is now the primary method for users to refine React InstantSearch results. From now on, you’re leveraging the complete Autocomplete ecosystem to bring a state-of-the-art search experience for desktop and mobile.
You can now add Autocomplete everywhere on your site and redirect users to the search page whenever they submit a search or after they select a suggestion. You can also use context from the current page to personalize the autocomplete experience. For example, you could display a preview of matching results in a panel for each suggestion and let InstantSearch provide these results once on the search page.