Add dynamic search to react application using fuse.js

Time:2021-11-26

Fuse.js is a lightweight search engine that can run on the client side of the user‘s browser. Let’s see how to use it to easily add search capabilities to react applications.

When to use fuse.js

The search function is very useful for many types of websites, allowing users to find what they want efficiently. But why do we choose to use fuse. JS?

There are many options for driving search, and the simplest may be to use an existing database. For example, Postgres has a full-text search feature. MySQL and redis also have redissearch modules.

There are also some specialized search engines, among which elastic search and Solr are the most popular. These search engines need more settings, but they have advanced features that your use case may need.

Finally, you can use search as a service platforms such as algolia or swiftype. These services run their own search infrastructure. You only need to provide data, configuration and query through the API.

However, you may not need the capabilities exposed by these solutions, which may require a lot of work to implement, let alone cost. If there is not much data to search, fuse.js requires minimal settings and can provide a better search experience than you might think.

As for how much data is too much for fuse.js, considering that fuse.js needs to access the whole data set, you need to load it all on the client. If the size of the data set is 100MB, it is beyond the reasonable range sent to the client. But if it has only a few thousand bytes, it may be a good candidate for fuse. JS.

Build a fuse.js + react demo application

Let’s make a basic react application and use fuse.js to let users search for dog breeds. You can view the final results here, and the source code can be found on GitHub.

We’ll start by setting up some scaffolding. Starting with a new node.js project, we will install react and fuse.js:

npm install --save react react-dom fuse.js
//or
yarn add react react-dom fuse.js

We will also install parcel as a development dependency:

npm install --save-dev [email protected]
//or
yarn add --dev [email protected]

We willpackage.jsonUse it in the startup script to compile the application:

{  
  "scripts": {
    "start": "parcel serve ./index.html --open"
  }
}

Next, we will create a barebonesindex.html, which contains an emptydivFor react rendering, and anoscriptMessage to avoid blank pages when users disable JavaScript.

<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="app"></div>
    <noscript>
      <p>Please enable JavaScript to view this page.</p>
    </noscript>
    <script src="./index.js"></script>
  </body>
</html>

We will make ourindex.jsA simple start. We will render a form with search query input, although we will not actually process the search yet.

import React, { useState } from "react";
import ReactDom from "react-dom";

function Search() {
  return (
    <form>
      <label htmlFor="query">Search for a dog breed:</label>
      <input type="search" id="query" />
      <button>Search</button>
    </form>
  );
}

ReactDom.render(<Search />, document.getElementById("app"));

At this point, if you runnpm run startoryarn run start, parcel should open the website in the browser, and you should see this form.

Implement search

Now to implement the search, we’ll start with the component that displays the search results. We need to deal with three situations:

  1. When the user has not performed a search
  2. When there are no query results (because we don’t want users to think there are some problems)
  3. When will the results show

We will display all the results in the ordered list.

function SearchResults(props) {
  if (!props.results) {
    return null;
  }

  if (!props.results.length) {
    return <p>There are no results for your query.</p>;
  }

  return (
    <ol>
      {props.results.map((result) => (
        <li key={result}>{result}</li>
      ))}
    </ol>
  );
}

We also write our own search function. Later, we will be able to compare the results of our simple method with those of fuse.js.

Our method is simple: we will traverse the breed array (from this JSON list) and return all breeds that contain the entire search query. We will also make everything lowercase so that the search is case insensitive.

const dogs = [
  "Affenpinscher",
  "Afghan Hound",
  "Aidi",
  "Airedale Terrier",
  "Akbash Dog",
  "Akita",
  // More breeds..
];

function searchWithBasicApproach(query) {
  if (!query) {
    return [];
  }

  return dogs.filter((dog) => dog.toLowerCase().includes(query.toLowerCase()));
}

Next, let’s link everything together by getting the search query from the form submission, then executing the search and displaying the results.

function Search() {
  const [searchResults, setSearchResults] = useState(null);

  return (
    <>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          const query = event.target.elements.query.value;
          const results = searchWithBasicApproach(query);
          setSearchResults(results);
        }}
      >
        <label htmlFor="query">Search for a dog breed:</label>
        <input type="search" id="query" />
        <button>Search</button>
      </form>

      <SearchResults results={searchResults} />
    </>
  );
}

Add fuse.js

Using fuse.js is very simple. We need to import it and let it usenew Fuse()Index the data and then use the search function of the index. The search will return some metadata, so we will only extract the actual items for display.

import Fuse from "fuse.js";

const fuse = new Fuse(dogs);

function searchWithFuse(query) {
  if (!query) {
    return [];
  }

  return fuse.search(query).map((result) => result.item);
}

Metadata includes arefIndexInteger, which allows us to trace back to the corresponding items in the original dataset. If we usenew Fuse(dogs, {includeScore: true})After initializing the index, we will also get a matching score: a value between 0 and 1, where 0 is an exact match. Then the search results of “husky” will look like this:

[
  {
    item: "Siberian Husky",
    refIndex: 386,
    score: 0.18224241177399383
  }
]

We willSearchAdd a check box to the form of the component to let the user choose whether to use fuse.js instead of the basic search function.

<form
  onSubmit={(event) => {
    event.preventDefault();
    const query = event.target.elements.query.value;
    const useFuse = event.target.elements.fuse.checked;
    setSearchResults(
      useFuse ? searchWithFuse(query) : searchWithBasicApproach(query)
    );
  }}
>
  <label htmlFor="query">Search for a dog breed: </label>
  <input type="search" id="query" />
  <input type="checkbox" name="fuse" />
  <label htmlFor="fuse"> Use Fuse.js</label>
  <button>Search</button>
</form>

Now we can search with fuse. JS! We can use check boxes to compare using it with not using it.

The biggest difference is that fuse.js can tolerate typos (through approximate string matching), while our basic search requires exact matching. If we misspell “retriever” as “retriever”, please check the basic search results.

Add dynamic search to react application using fuse.js

Here are the more useful fuse.js results for the same query:

Add dynamic search to react application using fuse.js

Search multiple fields

If we care about multiple fields, our search may be more complex. For example, imagine that we want to search by variety and country of origin. Fuse. JS supports this use case. When we create an index, we can specify the object key to index.

const dogs = [
  {breed: "Affenpinscher", origin: "Germany"},
  {breed: "Afghan Hound", origin: "Afghanistan"},
  // More breeds..
];

const fuse = new Fuse(dogs, {keys: ["breed", "origin"]});

Now fuse. JS will search at the same timebreedandoriginField.

summary

Sometimes, we don’t want to spend resources to build a complete elasticsearch instance. When we have simple requirements, fuse.js can provide corresponding simple solutions. As we can see, it is also very simple to use it with react.

Even if we need more advanced functions, fuse.js allows different weights to be given to different fields, addingANDandORLogic, adjusting fuzzy matching logic, etc. The next time you need to add a search function to your app, you can consider using it.

Recommended Today

SQL exercise 20 – Modeling & Reporting

This blog is used to review and sort out the common topic modeling architecture, analysis oriented architecture and integration topic reports in data warehouse. I have uploaded these reports to GitHub. If you are interested, you can have a lookAddress:https://github.com/nino-laiqiu/TiTanI recorded a relatively complete development process in my hexo blog deployed on GitHub. You can […]