Often while building applications we want to add some sort of searching feature to help our users to find the thing they are looking for. In this article, we will learn how to build one in react using Fuse.js ↗️ and make it type-safe with Typescript.
If you are just instead in the code part then skip right here
Before we go ahead, I want to make it clear that in this article we would be using an external library to help us with the search but if you want the simplest approach to have a searching-like feature then you can just go ahead and use includes ↗️ method on the array to have straight forward search.
Of course, it has its limitations, for starters, it does exact string search without fuzzy search and it’s not text case insensitive by default but it’s definitely the simplest solution to have and I wanted to point it out before we go ahead.
Fuse.js is a powerful, lightweight fuzzy-search library, with zero dependencies.
Two main reasons I went ahead and incorporated Fuse.js search for my blog page as well is that it allows me to do fuzzy-search and that it has zero dependencies and the overall bundle size ↗️ is quite small.
So what does fuzzy search means? Well, fuzzy searching (more formally known as approximate string matching) is the technique of finding strings that are approximately equal to a given pattern.
You can read more in detail about fuzzy search on Wikipedia ↗️
Fuse.js uses the concept of relevance score to rank the results. This score is determined by three factors:
- Fuzziness score
- Key weight
- Field-length norm
You can read more about these concepts here ↗️
So go ahead and install Fuse.js library in your project with package manager of your choice. I’ll be using yarn so here’s the command for that.
Now we know which library we are going to use for searching, let’s build a reusable react hook that will help us build this functionality.
Let’s create a file called
useSearch.ts and lay out the input props and the output for this hook
We are taking the
dataSet as the prop which is the list of data in which we want to perform the search,
keys as another prop which will consist of the keys on which we want Fuse.js to perform the search.
Next, let’s check how we can import the library and instantiate it with our dataSet
We are importing the Fuse.js library using es6 import and instantiating it with the dataSet and the options. For options, we are adding the keys which we imported as props and setting
includeScore to true. We would use this score to determine which results we want to include in the search output. Read more about all the options supported by Fuse.js here ↗️.
As an additional tip, we are using
useMemo hook here so that we do not create the fuse instance again if the dataSet and the keys are the same.
Now let’s use this fuse instance to calculate the results for us based on the searchValue.
For results, we are again using the
useMemo hook to recalculate the results whenever the searchValue changes (and we are adding fuse and dataSet as they are used in this hook and hence are dependencies for it).
In this useMemo hook we will first check if we have anything in searchValue or not, if not then we want to simply return the dataSet (or whatever logic you have for showing initial values from dataSet).
But if we have some searchValue then we will use the
fuse.search function with this searchValue. This gives us back the results with the information about the
score and the actual
item in the dataSet.
Now based on the score value we will filter out the results which do not match our threshold.
In Fuse.js a score closer to 0 means it’s a perfect match and a score closer to 1 means it’s far away from the search value.
Once we have filtered out the results, we would map over and return the
item which is the individual item data in the dataSet.
So if we wire it up all together then the file should look like this
The usage is very easy now, in whichever component we want to add search feature we can import the hook and use it as mentioned below
Our hook is functional and ready to use, just one thing, the hook does not have information about what is the type of the dataSet and hence cannot infer what would be the return type of the hook as well.
We’ll fix this by using Typescript Generics ↗️ to help us with just that.
The type-safe version of this would look like this
Notice how we are using the
T to typecast the dataSet value in our hook. Now our hook is type-safe and our usage accordingly will change like this
With this the value of our results is type-safe and typescript won’t be mad anymore.
Hope you found this helpful. See you in another one 👋🏽