As of version 5.8.0 (javascript), Firestore doesn’t have support for full text search. As mentioned on their documentation, we could subscribe to third party providers which is Agolia or Elastic Search for full text search. That’s great however for some of us who don’t want to use those services as they’re expensive, don’t want to maintain another service just for that functionality or for some other reason. This story will demonstrate on how you could leverage the array-contains query to filter records and adding lazy loading for paging.
I assume that you are already familiar with how to set up a Firebase project else please refer to their documentation.
Creating the Application
Lets create a simple application which has a records of people and we should be able to filter out the person that we’re looking for. This type of applications applies to patient records, payroll, and etc wherein our users are looking for someone by searching their name.
Will start off by putting a text box for searching and a table for displaying the records. I already have attach an event listener on document loaded for our scripts to be added later on.
Generating Records
Lets generate 200 random names using tools like Mackaroo. It should have properties: name (object) and keywords (array). The keywords field will be populated with strings of all the possibilities of a name is being search. Take note that array-contains is case sensitive, it would be much better that our names are formatted in lowercase. Lets create a function that would generate those keywords.
Calling the generateKeywords([‘john’, ‘the’, ‘dough’, ‘jr’]) would produce the output of:
That’s a lot of strings. We pass this array into the keywords property. In real world applications this is done on when creating or updating a person’s record.
The empty string is included so that when we query on the people collection with the keywords field, we should expect to get all the records instead of nothing.
Going back to our 200 random JSON names. We’ll iterate to each one of it and add the keywords property with a value getting from the generateKeywords function. Then add the document in people collection onto Firestore.
After running that script, our collection should look something like this:
Implementing Search
Inside DOMContentLoaded event handler, lets create a function that takes a search as an argument which retrieves those documents from people collection and returns a markup to be pass on later to the tbody.
Invoke the searchByName with a empty string to display all people.
Run your application and you should see an error in the console saying:
Click on that link to build the index, wait for it to finish and then run or refresh your application again. You should see something like this:
This is where the purpose of the empty string we added on the generateKeywords function because by default our app should display all the people when passed by an empty string on searchByName function.
As we type in the search box, the table is not being filtered. We must attach a keyup event handler on the text box.
Try searching by last name, first name or first name first.
Now we have implemented text search through array-contains. Our job here is done if we are happy of returning all the records to your users.
What if we have thousands of records? 🤔
And we are on Spark Plan which only has 50,000 document reads a day. 😅
Then hundreds of users are accessing this page. 😨
Even worse, if we’re on Blaze Plan with thousands of records all being accessed by hundreds of users. Billing Spike 😱💸💸💸😭
We don’t want to end up like this.
Limit to the rescue
We’ll be using .limit(). Let’s update our searchByName function.
We changed the parameter from string to object which has search, and limit properties with default values.
We also have to change how we call our function.
Refresh your application and you should only see the first 50 people. As you type in the search box it filters out and still only displays first 50 people.
But what about those other people after the first 50. 🤔
Bring in Lazy Loading
We will implement a feature infinite scroll except that ours has finite number of records. It should have been called finite scroll. 😅
First we should be able to know when the vertical scroll bar is at the bottom.
When the vertical scroll bar reaches the bottom of the page, we must get the last name of the last person. Replace the “do something” comment with:
We need the last name of last person for query cursor which is how Firestore implements pagination. This gives as the ability set a starting point for looking up documents. Lets use the startAfter method. In our query .orderBy(‘name.last’) chain another method .startAfter(lastNameOfLastPerson)
And also add another property lastNameOfLastPerson on the parameter.
Now the searchByName functions will return records depending on the last name of the last person.
Lets pass this last name of last person into searchByName function and then update the existing table rows. The infinite scroll syntax should look like this:
Lets try it out. 😁
That is it. This application is hosted here.
Link for the source code.
If you have allergies with .innerHTML syntax, I’ve create another branch without using it.
Thank you for reading my first story. Happy to hear your reviews. 😇