I'm making a course on Laravel Sanctum: MasteringAuth.com

Fuzzy searching with Fuse.js in Laravel & InertiaJS applications

Constantin Druc ยท 08 Jun, 2021

[00:01] I already have some boilerplate in place; we have a text input for the search term, a button that when clicked should clear the search term, and finally we have a list that will display the results of our search.

[00:15] The first thing I'm going to do is to set up a v-model for the search term. And I'll add it in our data, and the initial value will be null.

[00:31] Then I will add the click event listener on the button that will call the reset method to clear the search input. So I'll do click and call reset. Now to define the method we'll do methods, reset, and this will set the term to null.

[00:55] So now if I go into the browser and type something, and click the x button, the search input resets; which is what we wanted.

[01:04] It would be nice though if this x button will only be visible if we have a search term. So let's go here and add a v-if="term". So now the x button is hidden and when I type in, it's visible.

[01:23] Next up let's import fuse; I'll do import Fuse from "fuse.js" (make sure you install it first using npm).

[01:34] The next step is to set up a new fuse instance in the component's created lifecycle hook; so I'll go here and say created, and here I'll this.fuse = new Fuse() and the first argument needs to be an array of objects representing your dataset - the items you need to search in. For now I'll paste in a few lessons I have in my clipboard.

[01:58] The second argument we need to pass in is the configuration object. The most important option is the keys. Here we instruct fuse what object keys to consider when performing the search. I'll add in the title and the description.

[02:14] Let's also make sure we add fuse to our data. So I'll grab this from here and add it here. And now that we have a term and a fuse instance, we can create a computed property that will return the search results.

[02:33] So I'll do computed, results, and here we can return if we have a term; so if term we can do this.fuse.search() and pass in the term; otherwise we return an empty array.

[02:56] Our next step is to loop through these results and display a list of links for the user to choose from.

[03:03] So I'll go up here and we can do v-for="(result, index) in results", set the index as the key, and to display the lesson title we can do {{ result.item.title }}, and for the series we can do {{ result.item.series }}, and of course this list should only be visible if we have a search term; so v-if="term". So now if I go in the browser and type in "inertia", we have results but we also have this message right here. And this should only appear when we don't have any results to display

[03:44] So here we'll do v-if="!results.length" and instead of this term we should show the actual term. Now if I go here and refresh, here it is. So the search is working but we still need to fix a couple of things if I type in something and then click outside, the list is still there. To fix that, we can attach a blur event listener on the search input and call the reset method.

[04:14] So I'll go to the input and say blur, call reset, and now if I type something and click outside, the search term is cleared. It would also be nice to be able to reset the search by pressing the escape key. So let's add that as well. @keyup.esc="reset()" and we can specify the escape key here, and call the reset method.

04:40 So now if I type something in and tap escape, the search term is cleared. However, because we are resetting the search whenever the user clicks outside the input, that also means that our links will no longer work, because by the time I click on them the list is gone. Here's what I mean. I type inertia and go to this lesson right here, and we are still on the same page.

05:08 To prevent that from happening, we can add here on our inertia link, we can add, @mousedown.prevent - and this will stop the blur event from being fired - meaning that we'll no longer trigger the reset method whenever we click on a result. So if I go in the browser and type in a lesson, and click on the result...we get a 404.

[05:38] Ih that's because I forgot to add in the href for the inertial link. So here we should have result.item.link. And give it another go, and here it is.

[05:59] Finally let's replace this hard-coded array with records from our database. To do that I'll open my HandleInertiaRequest middleware, scroll down to the share method, and add a new prop called searchItems. Here I will query the lessons and use map to transform the results such that we follow the same format we had in the hard-coded array.

[06:29] So I will do, return lesson, cursor and then I'll call map, that will receive a function with the lesson, and here I can return an array matching our structure - which is title, description, series - which will be series->name and the link, and here I'll call the route('lessons.show'), lesson, and this is the lesson's slug.

[07:17] Now if I go inside my component, I can replace all this with this.$page.props.searchItems

[07:31] If I refresh the page and type in something, I get all the results from the database. Well too many results. To reduce the number of results we can always do slice zero...let's say eight, and this will give us the first eight results.

[07:57] And here it is. One thing we should do is to cache the query results - otherwise this will always run whenever we refresh the page or navigate to another one.

[08:11] To cache this we can do cache, and use the facade, remember forever, and let's say search items. And here we can pass in a function that will return our results. If we look here we have 51 queries executed.

[08:33] Refresh, we get an error. Right here we need a semicolon, and we only have three queries executed

[08:45] However our problem is now that whenever a new lesson is added or deleted we still need to update these search items. And we can make that happen by listening to the saved and deleted events on the Lesson model.

[09:01] So I'll go in the lesser model and I'll do: booted, and here we call static, saved, and pass in a callback. And we'll tell the cache layer to forget our search items. So I'll do cache, and use the facade, forget searchItems and we'll do the same thing for the deleted event.

[09:34] So now if I refresh the page, we still have three queries but if I delete one lesson so php artisan tinker, lesson first, delete. Now on the next refresh the queries should run again and be cached. So refresh and we get this error, and that's because I forgot to call ->toArray() - because this right here will return a collection and we need to store an array.

[10:09] Let's try again we have 50 queries and on the next refresh we have three. So now we always get three queries. If I delete another lesson, refresh, we execute the queries again, refresh again - they are cached and that's it.

[10:31] That's how you can set up fuse.js to do fuzzy searching with inertia.js and Laravel. Bye.