tutorial // May 05, 2021
How to Implement Client-Side Search with Fuse.js
How to implement a client-side, real-time search using Fuse.js.

For some applications, running a full search server and wiring up an index is overkill. In others, it's impractical due to requirements like needing to be offline-only. While a rich search experience should by default be driven by a real search engine running on a server, in some cases, implementing client-side search is preferred.
Getting Started
To get started, for this tutorial, we're going to use the CheatCode Next.js Boilerplate as a starting point. To clone it, run:
Terminal
git clone https://github.com/cheatcode/nextjs-boilerplate.git
Next, cd
into the cloned project and install its dependencies:
Terminal
cd nextjs-boilerplate && npm install
Next, let's install the fuse.js
dependency via NPM:
Terminal
npm i fuse.js
Finally, let's run the project:
Terminal
npm run dev
Once all of that is complete, we're ready to get started.
Setting up our test data
First, in order to wire up our search we'll need some test data. We're going to use this list of countries from Github. Because our goal is to build this entirely client-side, we're going to create a static JavaScript file and place this content in it:
/lib/countries.js
export default [
{ code: "AF", name: "Afghanistan" },
[...]
{ code: "ZW", name: "Zimbabwe" },
];
Next, we're ready to start building out our search. To demonstrate the setup, we're going to add a /search
page in the boilerplate:
/pages/search/index.js
import React, { useState } from "react";
const Search = () => {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
return (
<div>
// We'll build out our search and results UI here...
</div>
);
};
Search.propTypes = {};
export default Search;
To get started, here we've created a skeleton React component using the function component pattern. At the top, we define our function component with const Search
. Just inside the function body, we utilize the useState()
hook in React to create two state values we'll need: searchQuery
and searchResults
.
A few things to note when we're using the useState()
hook:
- When we call to
useState()
the value we pass to it represents the default value (here, forsearchQuery
we pass an empty string and forsearchResults
we pass an empty array). - A call to
useState()
returns an array containing two values: the current value and a setter to update the value (here,searchQuery
is the name we use for the state value andsetSearchQuery
allows us to update that value).
Next, to create our base component, we return
an empty <div></div>
tag where the core of our search UI will go.
Initializing our index
Now, let's pull in our list of countries and create our search index using Fuse:
/pages/search/index.js
import React, { useState } from "react";
import Fuse from "fuse.js";
import countries from "../../lib/countries";
const Search = () => {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const searchIndex = new Fuse(countries, {
includeScore: true,
threshold: 0.4,
keys: ["name"],
});
return (
<div>
// We'll build out our search and results UI here...
</div>
);
};
Search.propTypes = {};
export default Search;
We've added a few things here. First, up at the top, we import the countries.js
file that we created earlier. Next, we create a new variable searchIndex
which is set to new Fuse()
passing it two things: our list of countries
(the data we want to add to the index) and an options
object with three settings:
includeScore
tells Fuse that we want each search result to receive a relevancy score and we want that score returned in the search results data.threshold
is a number which dictates how "fuzzy" our search should be. Athreshold
of0
means the search has to match exactly while athreshold
of1.0
means anything will match.0.4
is arbitrary here, so feel free to play with it.keys
is an array of strings describing the object keys we want to search. In this case, we only want our search to be against thename
property on each of our country objects.
Though it may not look like much, this is the core of working with Fuse. Simple, right? With this, now we're ready to set up a search UI and see some real-time results.
Wiring up the search UI
First, we need to add an <input />
where a user can type in a search query:
/pages/search/index.js
import React, { useState } from "react";
import Fuse from "fuse.js";
import countries from "../../lib/countries";
const Search = () => {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const searchIndex = new Fuse(countries, {
includeScore: true,
threshold: 0.4,
keys: ["name"],
});
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery);
const results = searchIndex.search(searchQuery);
setSearchResults(results);
};
return (
<div>
<div className="mb-4">
<input
type="search"
name="search"
className="form-control"
value={searchQuery}
onChange={(event) => handleSearch(event.target.value)}
/>
</div>
</div>
);
};
Search.propTypes = {};
export default Search;
We're adding two big things here: first, down in the return
value (our component's markup), we've added an <input />
tag with a type of search
(this toggles the browser's special features for a search input like a clear button).
We've also given it a className
of form-control
to give it some base styling via Bootstrap (included in the boilerplate we're using). Next, we set the input's value
to our searchQuery
state value and then add an onChange
handler, passing a function which calls to another function we've defined up above, handleSearch()
, passing the event.target.value
which represents the current value typed into the search input.
/pages/search/index.js
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery);
const results = searchIndex.search(searchQuery);
setSearchResults(results);
};
Zooming in on that handleSearch()
function, this is where the magic happens. First, we make sure to set our searchQuery
(event.target.value
, passed into the handleSearch
function as searchQuery
) so that our UI updates as the user types. Second, we perform our actual search, using the .search()
method returned as part of the Fuse index instance (what we store in the searchIndex
variable).
Finally, we take the results
we get back from Fuse and then set those on to state. Now, we're ready to render our results and see this whole thing work in real-time.
Wiring up the results UI
To finish up, next, we need to render out our search results. Remember that earlier as part of the options object we passed to Fuse, we added an includeScore
setting, set to true
. Before we render our search results, we want to create a sorted version of the results, based on this score
value.
/pages/search/index.js
import React, { useState } from "react";
import Fuse from "fuse.js";
import countries from "../../lib/countries";
const Search = () => {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const sortedSearchResults = searchResults.sort((resultA, resultB) => {
return resultA.score - resultB.score;
});
const searchIndex = new Fuse(countries, {
includeScore: true,
threshold: 0.4,
keys: ["name"],
});
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery);
const results = searchIndex.search(searchQuery);
setSearchResults(results);
};
return (
<div>
<div className="mb-4">
<input
type="search"
name="search"
className="form-control"
value={searchQuery}
onChange={(event) => handleSearch(event.target.value)}
/>
</div>
</div>
);
};
Search.propTypes = {};
export default Search;
Here, we've added a sortedSearchResults
variable just benath our useState()
declaration for the searchResults
variable. Assigned to it is the result of calling searchResults.sort()
(the native JavaScript Array .sort()
method). To it, we pass a comparison function which takes in two arguments: the current item we're comparing resultA
(the one being iterated over in the sort) and the next item after it resultB
.
Our comparison is to check the difference between each score. Automatically, the .sort()
method will use this to give us back a sorted copy of our search results array, by each result's score
property.
Now we're ready to render the results. Let's add some boilerplate code and then walk through it:
/pages/search/index.js
import React, { useState } from "react";
import Fuse from "fuse.js";
import countries from "../../lib/countries";
const Search = () => {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const sortedSearchResults = searchResults.sort((resultA, resultB) => {
return resultA.score - resultB.score;
});
const searchIndex = new Fuse(countries, {
includeScore: true,
threshold: 0.4,
keys: ["name"],
});
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery);
const results = searchIndex.search(searchQuery);
setSearchResults(results);
};
return (
<div>
<div className="mb-4">
<input
type="search"
name="search"
className="form-control"
value={searchQuery}
onChange={(event) => handleSearch(event.target.value)}
/>
</div>
{sortedSearchResults.length > 0 && (
<ul className="list-group">
{sortedSearchResults.map(({ item }) => {
return (
<li className="list-group-item" key={item.name}>
{item.name} ({item.code})
</li>
);
})}
</ul>
)}
</div>
);
};
Search.propTypes = {};
export default Search;
This finishes out our search UI. Here, we've taken the sortedSearchResults
we created and first check to see if it has a length greater than 0
. If it does, we want to render our search results <ul></ul>
. If not, we want it to hide. For that list, we've used the Bootstrap list-group
to give our search results some style along with the list-group-item
class on each of our individual search results.
For each search result, we just render the name
and code
(in parentheses) side-by-side.
That's it! Now, if we load up our app in the browser and head to http://localhost:5000/search
, we should see our working search UI.
Wrapping up
In this tutorial, we learned how to build a client-side, real-time search using Fuse. We learned how to set up a simple search component in React, create a search index with Fuse (populating it with data in the process), and performing a search query against that index.