Concepts / Building Search UI / Conditional requests
Aug. 09, 2019

Conditional Requests

Overview

InstantSearch sends a request to Algolia’s servers on every keystroke, which means end users see updated results in real time. Additionally, InstantSearch makes another first request to show the initial results on an empty query. This is the default behavior, which also warms up the network connection and makes subsequent requests faster.

However, there are times when you don’t want to perform more network calls than strictly necessary. For example, when you don’t want to display initial results. This guide helps you build a UI that prevents the initial request on an empty query. You can find the complete example on GitHub.

How it works

InstantSearch is the UI part that sits on top of a search client, with a state managed by the Helper. These three layers are composable, and can be interchanged to leverage the InstantSearch widgets system with a different search client.

The Algolia search client queries Algolia’s servers whenever the end user refines the search. It is possible to build your own search client to implement custom behaviors. This is the approach we take to do back-end search with InstantSearch.

To create your own client, you need to implement a given interface that receives and returns formatted data that InstantSearch can understand.

Implementing a proxy

To prevent searches from happening in certain use cases, we need to wrap a proxy around the Algolia search client. We then pass our custom client to instantsearch():

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
const algoliaClient = algoliasearch(
  'YourApplicationID',
  'YourSearchOnlyAPIKey'
);

const searchClient = {
  search(requests) {
    return algoliaClient.search(requests);
  },
};

const search = instantsearch({
  indexName: 'instant_search',
  searchClient,
});

search.addWidget(
  instantsearch.widgets.searchBox({
    container: '#searchbox',
  })
);

search.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
  })
);

search.start();

Functionally speaking, this is no different from directly passing the original client. However, this proxy allows us to perform logic before calling the search method. In our case, we’ll be able to call it conditionally, thus not performing the query when not necessary.

Mocking a search request

We don’t want to perform a search request when the query is empty (""), so we first need to detect it:

1
2
3
4
5
6
7
8
9
const searchClient = {
  search(requests) {
    if (requests.every(({ params }) => !params.query)) {
      // Here we have to do something else
    }

    return algoliaClient.search(requests);
  },
};

In some cases, such as when the end user clicks on a refinementList, this can trigger multiple requests. We therefore need to make sure that every query is empty before we intercept the function call.

Then, we have to return a formatted response. This is an array of objects of the same length as the requests array. At the bare minimum, each object needs to contain: hits, nbHits and processingTimeMS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const searchClient = {
  search(requests) {
    if (requests.every(({ params }) => !params.query)) {
      return Promise.resolve({
        results: requests.map(() => ({
          hits: [],
          nbHits: 0,
          processingTimeMS: 0,
        })),
      });
    }

    return algoliaClient.search(requests);
  },
};

Finally, we can use the proxy with the instantsearch() widget, like this:

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
const algoliaClient = algoliasearch(
  'YourApplicationID',
  'YourSearchOnlyAPIKey'
);

const searchClient = {
  search(requests) {
    if (requests.every(({ params }) => !params.query)) {
      return Promise.resolve({
        results: requests.map(() => ({
          hits: [],
          nbHits: 0,
          processingTimeMS: 0,
        })),
      });
    }

    return algoliaClient.search(requests);
  },
};

const search = instantsearch({
  indexName: 'instant_search',
  searchClient,
});

search.addWidget(
  instantsearch.widgets.searchBox({
    container: '#searchbox',
  })
);

search.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
  })
);

search.start();

You can find the complete source code of the example on GitHub.

Did you find this page helpful?