Input component value laging one change behind

Ember 3.28

I have the following Input use :

    <Input @type="text" @size="40"
      @class="form-control"
      @placeholder="Chercher un client"
      @value={{ this.search }}
      {{on "input" this.performSearch  }}
    /> 

It is used in the controller like this :

  @tracked search=""; 

  @tracked instructionsList = null;

  @action async performSearch() {
    let queryParams={
      page: { number: 1, size: 10},
      search: this.search
    }
     const instructions = this.store.query('instruction', queryParams);
     this.instructionsList = instructions;
  }

It works, except it display the result one keystroke late.

If I have an entry named Boat, typing the b will display search result for ‘’, then typing o will display search result for ‘b’ and typing ‘a’ will display the search result of bo, etc…

How can I remove the lag ?

I’m not too familiar with <Input>, but is there a reason <input> (native HTML element) wouldn’t work?

I have a demo here, if you’re curious

Which is a Polaris version of what you’d today in a default app:

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class Demo extends Component {
  @tracked search = "";
  @tracked submitCount = 0;

  @action
  async performSearch(e) {
    let currentValue = e.target.value;
    this.submitCount++;

    console.log({ currentValue }, this.submitCount);
  }
}
    <input type="text" size="40"
      placeholder="Chercher un client"
      value={{this.search}}
      {{on "input" this.performSearch}}
    />

    <br>
    {{this.search}}<br>
    Submit count: {{this.submitCount}}
1 Like

I have replaced Input by input and search is not binded to the value of the textfield. It seems the binding of the value is the main difference.

The limber seems to not be able to use component as Input

After many try it seems my problem was the event I was using. “keyup” solved the problem. So for a input text that trigger an action dynamically the correct way to do it is :

    <Input @type="text" @size="40"
      @value={{ this.search }}
      {{on "keyup" this.performSearch  }}
    /> 

Your problem is that oninput fires before your own this.search has been updated.

The performSearch method is listening to the real browser oninput event. The built-in <Input> component also uses this event in order to update your this.search. The order of these two event handlers happens to be such that your performSearch runs first.

The smallest fix is to use the event argument to performSearch:

performSearch(event) {
  let queryParams={
    page: { number: 1, size: 10},
    search: event.target.value
  }
   const instructions = this.store.query('instruction', queryParams);
   this.instructionsList = instructions;
}

Alternatively, you can avoid doing any manual event handling at all if you derive your instructionsList from your search:

import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service';
import { cached } from '@glimmer/tracking';

export default class ApplicationController extends Controller {
  @service store;
  @tracked search = '';

  @cached get instructionsList() {
    let queryParams = {
      page: { number: 1, size: 10 },
      search: this.search,
    };
    return this.store.query('instruction', queryParams);
  }
}

The cached here ensures that we only trigger a new query when the search has actually changed. Without it, we will trigger new queries every time anything causes a re-render.

This version is slightly different in that it runs the query even before the user has typed anything. If that’s not desirable, you can have an if inside instructionsList() that prevents it from running the query when search is empty.

1 Like