Search as you type at 60fps with js-coroutines

miketalbot

Mike Talbot โญ

Posted on July 6, 2020

Search as you type at 60fps with js-coroutines

It's nice to be able to make user interfaces that require the least number of clicks for the user to achieve their goal. For instance, we might want to search a list as we type. The challenge is though, as the list gets bigger there's a chance that the whole user experience will degrade as our JavaScript hogs the main thread stopping animations and making the whole experience glitchy.

This article will show how we can quickly modify a standard search function to use js-coroutines and keep the fully responsive experience with very little extra effort.

Let's say we have a list of 1,000,000 items and we have a text box, as the user types, we'd like to return the first 50 entries that have words that match the words they've typed (in any order).

For this example, we'll use "unique-names-generator" to create a list of nonsense to search on! Entries will look a little like this:

Aaren the accused lime flyingfish from Botswana
Adriana the swift beige cuckoo from Botswana
Enter fullscreen mode Exit fullscreen mode

Our search function is pretty simple:

function find(value) {
    if (!value || !value.trim()) return []
    value = value.trim().toLowerCase()
    const parts = value.split(" ")
    return lookup
        .filter(v =>
            parts.every(p =>
                v.split(" ").some(v => v.toLowerCase().startsWith(p))
            )
        )
        .slice(0, 50)
}
Enter fullscreen mode Exit fullscreen mode

But with 1,000,000 entries the experience is pretty woeful. Try searching the in the screen below for my favourite dish: 'owl rare', and watch the animated progress circle glitch...

This experience is atrocious and we'd have to either remove the functionality or find a much better way of searching.

js-coroutines to the rescue!

With js-coroutines we can just import the filterAsync method and re-write our "find" to be asynchronous:

let running = null
async function find(value, cb) {
    if (running) running.terminate()
    if (!value || !value.trim()) {
        cb([])
        return
    }
    value = value.trim().toLowerCase()
    let parts = value.split(" ")
    let result = await (running = filterAsync(
        lookup,

        v =>
            parts.every(p =>
                v.split(" ").some(v => v.toLowerCase().startsWith(p))
            )
    ))
    if (result) {
        cb(result.slice(0, 50))
    }
}

Enter fullscreen mode Exit fullscreen mode

Here you can see we terminate any currently running search when the value changes, and we've just added a callback, made the function async and that's about it.

The results are much better:

Alt Text

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
miketalbot
Mike Talbot โญ

Posted on July 6, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About