UI Libraries / Autocomplete / Debouncing sources

Autocomplete supports as-you-type search, and, by default, processes sources for each keystroke. It provides a real-time experience with instant feedback. Yet, there are situations when as-you-type might either not be desirable or even possible.

For example, if you’re using Algolia and want to control your search operations usage, you might want to wait until the user stops typing before triggering a search request. Similarly, when you’re working with rate-limited services such as the GitHub API, sending too many requests in a short amount of time can result in errors. In such cases, you can debounce your sources to mitigate how many requests Autocomplete triggers.

Another concern you might have is accessibility. As-you-type search results mean the UI changes at a high rate. When the sources are asynchronous, you can’t control the pace at which results come back. It can create a sub-optimal experience for users with vestibular disorders. Conditionally debouncing your autocomplete can be a great way to honor your user’s preference for reduced motion.

Writing an asynchronous debouncing function

In Autocomplete, you can return static or dynamic items. In both cases, you need Autocomplete to “wait” for a set time to pass without any typing before a result is returned.

To do so, you can write a debouncing function that returns a promise. When the timeout has elapsed, the promise resolves to the passed items.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { autocomplete } from '@algolia/autocomplete-js';

function debouncePromise(fn, time) {
  let timerId = undefined;

  return function debounced(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }

    return new Promise((resolve) => {
      timerId = setTimeout(() => resolve(fn(...args)), time);
    });
  };
}

The debouncePromise function lets you create a wrapper around any asynchronous function and hold it off for a specified amount of time.

1
const debounced = debouncePromise((items) => Promise.resolve(items), 300);

You can change the time to wait to whatever makes sense for your use case.

Debouncing sources

You can use the debounced function to wait on static and dynamic sources, including Algolia ones.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ...

autocomplete({
  container: '#autocomplete',
  getSources({ query }) {
    return debounced([
      {
        sourceId: 'algoliaItems',
        getItems() {
          return getAlgoliaResults({
            searchClient,
            queries: [
              {
                indexName: 'indexName',
                query,
              },
            ],
          });
        },
        // ...
      },
      {
        sourceId: 'staticItems',
        getItems({ query }) {
          const items = [
            { label: 'Twitter', url: 'https://twitter.com' },
            { label: 'GitHub', url: 'https://github.com' },
          ];

          return items.filter(({ label }) =>
            label.toLowerCase().includes(query.toLowerCase())
          );
        },
        // ...
      },
      {
        sourceId: 'dynamicItems',
        getItems() {
          return fetch(`https://example.org/search/?q=${query}`)
            .then((response) => response.json())
            .then(({ results }) => results)
            .catch(() => []);
        },
        // ...
      },
    ]);
  },
});
Did you find this page helpful?