DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Build a Pokedex React App with a Slash GraphQL Backend
  • Mastering React App Configuration With Webpack
  • How to Build a React Native Chat App for Android
  • Controlling Access to Google BigQuery Data

Trending

  • Streamlining Event Data in Event-Driven Ansible
  • AI Meets Vector Databases: Redefining Data Retrieval in the Age of Intelligence
  • Docker Model Runner: Streamlining AI Deployment for Developers
  • Hybrid Cloud vs Multi-Cloud: Choosing the Right Strategy for AI Scalability and Security
  1. DZone
  2. Data Engineering
  3. Databases
  4. React Autosuggest Search With Google Firestore

React Autosuggest Search With Google Firestore

A seasoned developer gives a tutorial on how to create a custom autosuggest search feature for your site using React and FirestoreDB.

By 
Amit Jambusaria user avatar
Amit Jambusaria
·
Jan. 04, 21 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
21.9K Views

Join the DZone community and get the full member experience.

Join For Free

Search with autocomplete is a standard feature in most apps. It is used for searching through a catalog of resources such as products, services, airports, hotels, cars, posts, etc. This capability does not come built-in with Google Firestore DB. Firestore DB doesn't support keystroke search or native indexing for text fields in documents. Furthermore, loading an entire collection on the client or a cloud function just to search on certain fields is not prudent. It can cause performance degradation as well as increase the overall cost via expensive queries since Firestore’s pricing model is based on a per-document read model. 

Firebase recommends using a third-party service like Algolia or Elasticsearch to enable full text search or keystroke search for Firestore data. Integration with Algolia requires configuring the Algolia client with your App ID and API key. Alogia is also a paid service with very limited options in the free plan. If you don’t want to go through the overhead of setting up and paying for Algolia or Elasticsearch, a simple workaround for keystroke/typeahead search can be implemented on Cloud Firestore using searchable data structures and some special data querying methods. 

Before we get into it, this article assumes you have a basic understanding of working with React apps and have some experience working with Google Firebase and Firestore DB. If not, I strongly encourage doing some tutorials on how to build React apps as well as dipping your feet in Google Firebase and Firestore. Onwards to the article then. 

Now, let’s suppose we want to implement a Country autocomplete search component as shown below. 

To build this Autocomplete search dropdown, we will use the following in our React app:

  1. List of countries in the dropdown data sourced from countries.json, provided here - https://gist.github.com/amitjambusaria/b9adebcb4f256eae3dfa64dc9f1cc2ef
  2. React Autosuggest - https://react-autosuggest.js.org
  3. Google Firestore database

React Autosuggest Setup

The dropdown options are the countries are not preloaded on the client side. This is done to avoid overloading the DOM with thousands of options, which can lead to sluggishness and performance degradations. Instead, with each keystroke, a network call is made with a search query to fetch a filtered subset of options. 

As seen above, with each keystroke, a GET call is made to the Firestore database with the search keywords. The front-end React Autosuggest component has a prop called onSuggestionsFetchRequested, wherein you can pass a method that can fetch the search-specific options from the server; in our case, data/options will be directly fetched from the Firestore database. The following are some of other key props that come with React Autosuggest: 

Prop

Type

Required

Description

suggestions

Array

✓

These are the suggestions that will be displayed. Items can take an arbitrary shape.

onSuggestionsFetchRequested

Function

✓

Will be called every time you need to recalculate suggestions.

onSuggestionsClearRequested

Function

✓

Will be called every time you need to set suggestions to [].

getSuggestionValue

Function

✓

Implement it to teach Autosuggest what should be the input value when a suggestion is clicked.

renderSuggestion

Function

✓

Use your imagination to define how suggestions are rendered.

inputProps

Object

✓

Pass through arbitrary props to the input. It must contain value and onChange.

Click here for the full list of props. Given below is the front-end search component that leverages React Autosuggest.

JavaScript
 




x


 
1
import React, { FC, useState, useEffect, useCallback, useMemo, ChangeEvent } from 'react';
2
import Autosuggest from 'react-autosuggest';
3

          
4
import 'vendors/react-autosuggest.css';
5
import { useDatastore } from 'auth.provider';
6
import { Country, CountryService } from 'services/country.service';
7

          
8
export type onLocationSelect = (country: Country) => void;
9

          
10
type LocationAutocomplete = {
11
  uid: string;
12
  text: string;
13
  initialValue?: string;
14
  placeholder?: string;
15
  onSelect: onLocationSelect;
16
};
17

          
18
const LocationAutosuggest = Autosuggest as new () => Autosuggest<Country>;
19

          
20
export const LocationAutocomplete: FC<LocationAutocomplete> = ({
21
  uid,
22
  text,
23
  initialValue = '',
24
  placeholder = 'Enter country name',
25
  onSelect,
26
}) => {
27
  const store = useDatastore();
28
  const service = new CountryService(store);
29

          
30
  const [value, setValue] = useState(initialValue);
31
  const [suggestions, setSuggestions] = useState([] as Country[]);
32

          
33
  useEffect(() => {
34
    setValue(initialValue);
35
  }, [initialValue]);
36

          
37
  const onChange = useCallback(
38
    (_: ChangeEvent, { newValue }: { newValue: string }) => {
39
      setValue(newValue);
40
    },
41
    [setValue],
42
  );
43

          
44
  const selectSuggestion = useCallback(
45
    (country: Country) => {
46
      onSelect(country);
47
      return country.name;
48
    },
49
    [onSelect],
50
  );
51

          
52
  const clearSuggestions = useCallback(() => {
53
    setSuggestions([]);
54
  }, [setSuggestions]);
55

          
56
  const fetchSuggestions = useCallback(
57
    ({ value }: { value: string }) => {
58
      if (value.length > 1) {
59
        service.fetchCountries(value).then(setSuggestions).catch(console.error);
60
      }
61
    },
62
    [service],
63
  );
64

          
65
  const renderSuggestion = useCallback((country: Country) => {
66
    return (
67
      <div tw="flex items-center text-sm">
68
        <img src={country.flag} alt="" tw="w-6 rounded-sm mr-4 pointer-events-none" />
69
        <span>{country.name}</span>
70
      </div>
71
    );
72
  }, []);
73

          
74
  const inputProps = useMemo(() => ({ onChange, placeholder, value }), [onChange, placeholder, value]);
75

          
76
  return (
77
    <>
78
      <label tw="block mb-1" htmlFor={uid}>
79
        {text}
80
      </label>
81
      <LocationAutosuggest
82
        getSuggestionValue={selectSuggestion}
83
        inputProps={inputProps}
84
        multiSection={false}
85
        onSuggestionsClearRequested={clearSuggestions}
86
        onSuggestionsFetchRequested={fetchSuggestions}
87
        renderSuggestion={renderSuggestion}
88
        suggestions={suggestions}
89
      />
90
    </>
91
  );
92
};


Cloud Firestore Setup

Keystroke search is not supported natively by Firestore. To get around this limitation, we will have to seed the search options in a creative way. This solution is ideal when you want to search on just one or two properties of your document and don’t want to invest in paid services like Algolia or Elastic Search. 

In our app, since we are searching for countries, we will have to seed the Country Collection in our Firestore DB. But simply seeding the data is not enough. For type-ahead autocomplete we need the ability to search the document based on the country “name” property. Loading the entire collection and performing the search directly on the client-side is not feasible, because, depending on the size of the collection, it could cause slowness. It can also get very expensive since Firstore’s pricing model is based on a per document read model, so when you load an entire collection for each user the costs can quickly add up.

The answer lies in creating a searchableKeywords object for the field we want to search on. In our app, the country "name" string will be broken into substring elements and stored as an Array on each individual document. We can then query Firestore on these substrings. For example, the searchableKeywords for the country “Canada” would be:

Plain Text
 




xxxxxxxxxx
1


 
1
[
2
  0 => “c”
3
  1 => “ca”
4
  2 => “can”
5
  3 => “cana”
6
  4 => “canad”
7
  5 => “canada”
8
]


The searchableKeywords Array is created using a seeding script. This script is meant to be run as Admin against your Firestore DB. When executed, the generateKeywords method (line 29) computes the searchableKeywords Array with the country name substrings. This Array is added to each Country document. Finally, the entire Country Collection is committed to Firestore (line 53-64).

JavaScript
 




xxxxxxxxxx
1
63


 
1
import admin from 'firebase-admin';
2
import countries from './data/countries.json';
3

          
4
const ENV = process.env;
5
const serviceAccountConfig = {
6
  type: ENV.SA_TYPE,
7
  project_id: ENV.SA_PROJECT_ID,
8
  private_key_id: ENV.SA_PRIVATE_KEY_ID,
9
  private_key: ENV.SA_PRIVATE_KEY,
10
  client_email: ENV.SA_CLIENT_EMAIL,
11
  client_id: ENV.SA_CLIENT_ID,
12
  auth_uri: ENV.SA_AUTH_URI,
13
  token_uri: ENV.SA_TOKEN_URI,
14
  auth_provider_x509_cert_url: ENV.SA_AUTH_PROVIDER_X509_CERT_URL,
15
  client_x509_cert_url: ENV.SA_CLIENT_X509_CERT_URL,
16
} as admin.ServiceAccount;
17

          
18
admin.initializeApp({
19
  credential: admin.credential.cert(serviceAccountConfig),
20
  databaseURL: ENV.REACT_APP_DATABASE_URL
21
});
22

          
23
const firestore = admin.firestore();
24
const batch = firestore.batch();
25

          
26
// Ex: country name "India" will return ['i', 'in', 'ind', 'indi, 'india']
27
// This is needed for full-text search in country autocomplete
28
// TODO - convert this into clound function (on doc create) or use a 3rd party service for locations
29
const generateKeywords = (countryName: string) => {
30
  const wordArr = countryName.toLowerCase().split(' ');
31
  const searchableKeywords = [];
32
  let prevKey = '';
33
  for (const word of wordArr) {
34
    const charArr = word.toLowerCase().split('');
35
    for (const char of charArr) {
36
      const keyword = prevKey + char;
37
      searchableKeywords.push(keyword);
38
      prevKey = keyword;
39
    }
40
    prevKey = '';
41
  }
42

          
43
  return searchableKeywords;
44
};
45

          
46
const countriesCollection = firestore.collection('countries');
47
countries.forEach((country) => {
48
  const searchableKeywords = generateKeywords(country.name);
49
  batch.set(countriesCollection.doc(`${country.code}_${country.region}`), {
50
    ...country,
51
    searchableKeywords,
52
    visibility: 'public',
53
  });
54
});
55

          
56
batch
57
  .commit()
58
  .then(() => {
59
    console.log('Collection successfully written!');
60
  })
61
  .catch((error) => {
62
    console.error('Error writing collection: ', error);
63
  });


Once the searchableKeywords are in place, you can query it using the array-contains Firestore operator. When the user types “can” in the search field, all Countries which have “can” in their searchableKeywords Array will be returned as autocomplete suggestions. Given below is the query to fetch suggestions.

JavaScript
 




xxxxxxxxxx
1
18


 
1
export class CountryService {
2
  constructor(private store: firebase.firestore.Firestore) {}
3

          
4
  public fetchCountries = async (query: string) => {
5
    const { docs } = await this.store
6
      .collection('countries')
7
      .where('searchableKeywords', 'array-contains', query.trim().toLowerCase())
8
      .get();
9

          
10
    return docs.map((doc) => doc.data() as Country);
11
  };
12

          
13
  public fetchCountryByCode = async (query: string) => {
14
    const { docs } = await this.store.collection('countries').where('code', '==', query.toUpperCase()).get();
15
    return docs[0].data() as Country;
16
  };
17
}


Conclusion

This approach for autocomplete keystroke search is very easy to setup and maintain in Firestore. It is pretty barebones and not as feature-rich as Algolia or Elasticsearch. Another drawback is that the user has to enter their search term correctly and it does not handle spelling errors. Having said that, if your needs are simple and you want something for free, this can be a quick and elegant solution for your app.

Database React (JavaScript library) Google (verb) app

Opinions expressed by DZone contributors are their own.

Related

  • How to Build a Pokedex React App with a Slash GraphQL Backend
  • Mastering React App Configuration With Webpack
  • How to Build a React Native Chat App for Android
  • Controlling Access to Google BigQuery Data

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!