Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: inquirer/search - debounce support #1490

Closed
tanepiper opened this issue Jul 30, 2024 · 4 comments
Closed

[Feature]: inquirer/search - debounce support #1490

tanepiper opened this issue Jul 30, 2024 · 4 comments

Comments

@tanepiper
Copy link

tanepiper commented Jul 30, 2024

Description:

It would be good to have a native debouce value for the search module. Currently I am doing this to handle it:

public async search() {
  console.log(kleur.bold('Press Ctrl+C to exit the search'));
  const answer = await search<string>({
    pageSize: 5,
    message: 'Search for an Product Name',
    theme: {
      helpMode: 'always',
      prefix: '🔍',
    },
    source: async (input: string, { signal }: { signal: AbortSignal }) => {
      if (!input) {
        return [];
      }

      // Clear the previous timeout if there's any
      if (this.debounceTimeout) {
        clearTimeout(this.debounceTimeout);
      }

      // Return a promise that resolves after the debounce period
      return new Promise((resolve) => {
        this.debounceTimeout = setTimeout(async () => {
          try {
            const products = await this.client.itemsSearch(input, signal);

            const results =
              products?.map(({ content }, index) => ({
                name: content.name,
                value: content.id,
                description: `Record ${index + 1} / ${products.length} | ${content.id}) `
              })) || [];
            // Would have added this for pagination
            //results.push({
            //  name: 'Next Results',
            //  value: null,
            //  description: 'Select to load the next page',
            //  disabled: false,
            //});
            resolve(results);
          } catch (error) {
              console.error('\n Error during search:', error.message);
              resolve([]);
          }
        }, 1000); // 1000ms debounce period
      });
    },
  });
  return answer;
}
@SBoudrias
Copy link
Owner

I think you could simplify that using the promise interface of node:timers https://nodejs.org/api/timers.html. And relying on the signal status to proceed of not after the timeout.

import { setTimeout } from 'node:timers/promises';
import { search } from '@inquirer/prompts';

const answer = await search({
  message: 'Select an npm package',
  source: async (input, { signal }) => {
    await setTimeout(300);
    if (signal.aborted) return [];

    // Do the search
    fetch(...)
  },
});

IMO that's a simple piece of code that can be implemented in the user land. Let me know if that'd work (I'll document the pattern as a recipe.)

@tanepiper
Copy link
Author

@SBoudrias Yes that's a nice recipe! Thanks

@paul-uz
Copy link

paul-uz commented Aug 14, 2024

Whats this supposed to do exactly?

I have an issue where if i type too fast, the result from an old search string (current input minus 1 character) are shown

@SBoudrias
Copy link
Owner

Debouncing will reduce load on API calls, ignoring certain keystroke until the user is done typing. If you're interacting with external service this can be important.

But it's not there by default ATM since not every use-case rely on network calls.

Side note, if you think you found a race condition, I'd be interested in reviewing a reproductible case. There shouldn't be race condition - so if there's one, it's a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants