React-instantsearch: Backend InstantSearch triggers multiple requests

Created on 24 Sep 2019  路  11Comments  路  Source: algolia/react-instantsearch

Describe the bug 馃悰

We're using the Backend InstantSearch approach to build a middleware that will handle users authorisation (if and how many results they鈥檙e entitled to see on the frontend). The frontend is using a custom Algolia client, as suggested here, instead of the default one.

The frontend is made of custom widgets/connectors that allow to select multiple facets without triggering the refinement - until the user clicks on a Confirm button - hence allowing the system to send only a single request to Algolia.

Unfortunately, since we switched from the default Algolia client to the custom one, we can see that a request is being sent to the backend on every user's interaction.

To Reproduce 馃攳

Steps to reproduce the behaviour:

  1. Open the Location dropdown
  2. Tick multiple checkboxes without clicking the Confirm button
  3. Notice that multiple requests are being sent to the institutions endpoint in the Network tab

https://algolia-custom-client.netlify.com/

Expected behavior 馃挱

A request should only be sent when the Confirm button gets clicked.

Environment:

  • OS: macOS
  • Browser: Chrome
  • Version: 5.7.0

Most helpful comment

For future reference, custom clients need a "response cache" & "promise cache", which isn't fully specified in the docs yet. An example custom client implementing both is:

const cache = new Map();
const promiseCache = new Map();

const doRequest = body =>
  fetch('https://example.com/search', { method: 'post', body }).then(res =>
    res.json()
  );

const searchClient = {
  async search(requests) {
    const body = JSON.stringify({ requests });

    const cached = cache.get(body);

    if (cached) {
      return cached;
    }

    const promiseCached = promiseCache.get(body);

    if (promiseCached) {
      return promiseCached;
    }

    const promise = doRequest(body);

    promiseCache.set(body, promise);

    const response = await doRequest();

    cache.set(body, response);

    promiseCache.delete(body);

    return response;
  }
};

All 11 comments

A quick question before investigating, is there also source code for that demo? It wil help debugging what鈥檚 going on here

Hi @Haroenv,

yes, although there are few bits that can't be shared publicly, I'm afraid.

Let me know what's your preferred way that I can use to share the source code with you privately.

Thanks :)

I had a quick look at the InstantSearch code and I noticed this:

https://github.com/algolia/react-instantsearch/blob/f8c32f06c5fad1a6cf844dfc9f1ef9e6ab626fa7/packages/react-instantsearch-core/src/components/InstantSearch.js#L82-L84

the <InstantSearch> component calls the updateClient method on props change which, in turn, will call the search method

https://github.com/algolia/react-instantsearch/blob/f8c32f06c5fad1a6cf844dfc9f1ef9e6ab626fa7/packages/react-instantsearch-core/src/core/createInstantSearchManager.js#L64-L67

Perhaps passing a different requests object to the custom client on every user interaction will cause a change in the searchClient prop?

Some thing to note is that the default client has a cache, which we assume any client has, in the case of identical requests begin fired, this is something that you should consider as well :)

The cache would take care of identical requests being fired, I agree. However, the issue I'm describing here is slightly different: I need to trigger a refinement only on a specific user interaction (in this case on a button click), not on every single interaction, which is what the custom client is causing at the moment.

That behaviour is the same without a custom client, any refinement causes a new query to be sent. If that does not happen with the default client, maybe something else is causing rerenders that are unexpected. To help out there source code will be necessary

I've created a sandbox. Can I send you the link here? -> [email protected]

For future reference, custom clients need a "response cache" & "promise cache", which isn't fully specified in the docs yet. An example custom client implementing both is:

const cache = new Map();
const promiseCache = new Map();

const doRequest = body =>
  fetch('https://example.com/search', { method: 'post', body }).then(res =>
    res.json()
  );

const searchClient = {
  async search(requests) {
    const body = JSON.stringify({ requests });

    const cached = cache.get(body);

    if (cached) {
      return cached;
    }

    const promiseCached = promiseCache.get(body);

    if (promiseCached) {
      return promiseCached;
    }

    const promise = doRequest(body);

    promiseCache.set(body, promise);

    const response = await doRequest();

    cache.set(body, response);

    promiseCache.delete(body);

    return response;
  }
};

@Haroenv It looks like the extra response cache isn't necessary? Unless I'm missing something, you only really need the promiseCache.

Yes, for specifically this issue only the promise cache is useful @ycyvonne, however if you use this in general (e.g. backspace) you want it to be having a response cache too

Was this page helpful?
0 / 5 - 0 ratings