0

I have a component with search input, which displays the records' Names matched with the search keyword. I am using lightning-input type="search" with onchange method to change the property searchKey, and there is a wired apex method where I am passing this value. This is working fine.

But the problem is that every time user enters the key wired apex method is getting called. This is a very bad experience for the user and also it will make so many apex calls. To overcome this I tried adding the typing delay using the setTimeout method (keyboard debouncing). The setTimeout() is getting called and also it is setting the new value to the searchKey. But the wired method is not getting called.

Here is my code snippet.

JS

doneTypingInterval = 300;
searchKey = '';

@wire(fetchRecords, { searchKey: "$searchKey" })
wiredRecords(result) {
    // ...
}

// ...

handleSearchCriteriaChange(event) {

    clearTimeout(this.typingTimer);
    
    // eslint-disable-next-line @lwc/lwc/no-async-operation
    this.typingTimer = setTimeout(() => {
        this.searchKey = event.detail.value;

        console.log("value ", this.searchKey); // value is getting updated here.
    }, this.doneTypingInterval);
}

PS: I also tried the arrow function with setTimeout, not working as well. Same thing I have done with another component in the same org that is working. I have also changed the version of this component to match with others, still not working.


Please note that I am not running into any errors, also I have checked the debug logs on both the client-side and server-side. I don't see any error on browser console, and no logs at all in salesforce(I am sure the call is not being made to apex.).

4
  • 1
    Have you tried to add the async handleSearchCriteriaChange(event)? Mar 5, 2021 at 11:37
  • Thanks, @PabloFischer but adding async keyword did not help. Mar 5, 2021 at 15:27
  • P.S. "not working" is not helpful. What specifically do you observe? Do you get an error message? Have you checked the server logs? Are you sure this isn't simply a caching issue (e.g. the same term was used recently)?
    – sfdcfox
    Mar 5, 2021 at 15:36
  • @sfdcfox I have put logs at both server and client-side, I have checked at both the places, no logs are generated, and no error. Mar 5, 2021 at 15:39

3 Answers 3

1

A few potential issues here, which may or may not be relevant.

First, you didn't say you actually defined typingTimer in the class. It should be defined as a value:

export default class... {
  typingTimer;

Second, you should probably store the value you intend to send to create a closure:

  handleSearchCriteriaChange(event) {
    clearTimeout(this.typingTimer);
    let value = event.target.value;
    this.typingTimer = setTimeout(() => {
      this.searchKey = value;
    }, 500);
  }

Or query the field directly:

  handleSearchCriteriaChange(event) {
    clearTimeout(this.typingTimer);
    this.typingTimer = setTimeout(() => {
      this.searchKey = this.template.querySelector('input').value;
    }, 500);
  }

In my experiment, I found that not doing this caused... problems.

Third, keep in mind that if you use onchange, nothing will happen until a blur happens (in most cases). Use onkeyup instead.

Aside from that, the fact that it generally works in other components but not this one means you've oversimplified your code in the question, because it generally should work (assuming potential changes above).

2
  • Thanks! storing the value in a separate variable the trick, there were several other parameters that I was assigning using even.* which I had to store in separate variables. Do you know why this happens? if you could give any reference so I can read more on that. Mar 5, 2021 at 16:10
  • 1
    Also, please note that there is no need to define the typingTimer in the class as I am already updating the reactive property. I have also observed that sometimes there are some glitches in this method as well, and those I got rid of by putting async keyword on the function. Mar 5, 2021 at 16:14
3

I note the use of "self" as a hack to work around the loss of the "this" binding for the LWC component instance, relying on the closure to inherit the "self" value.

An arrow function should work, since "this" should not be messed with. Perhaps you left it using "self" which may have messed it up. If the latter wasn't the problem, you might be hitting an edge case (have a look at this blog post for a nice discussion on this and setTimeout in general JavaScript).

Personally I would avoid troubles with "this" and either:

Use an arrow function:

    this.typingTimer = setTimeout(() => {
        this.searchKey = event.detail.value;

        console.log("value ", this.searchKey); // value is getting updated here.
    }, this.doneTypingInterval);

Or use explicit binding:

    this.typingTimer = setTimeout(function () {
        this.searchKey = event.detail.value;

        console.log("value ", this.searchKey); // value is getting updated here.
    }.bind(this), this.doneTypingInterval);

Either approach should work. To be clear, you need to make sure to clean up the timer function before the LWC is disposed of, if you use bind, in order to avoid leaks. I'd say it's good practice to clean it up even when not using bind when the LWC is being destroyed. You can use the disconnectedCallback for this. See the lifecycle hooks documentation for more info.

4
  • P.S. bind is not recommended, as it can cause memory leaks. You are correct on the first front, though: arrow functions resolve most this issues.
    – sfdcfox
    Mar 5, 2021 at 15:02
  • @sfdcfox I extended my answer to cover that point.
    – Phil W
    Mar 5, 2021 at 15:07
  • I had already tried the arrow function, but that did not work, I also tried it now but no luck. The strange thing is that the same code((with arrow function) is working in my other components. Mar 5, 2021 at 15:18
  • BDW, thanks for explaining this concept. Mar 5, 2021 at 15:28
0

Reached out to our expert's team internally

Believe this is called "debouncing". Something like this should work

handleSearchTermChange(event) {
    // Debouncing this method: do not update the reactive property as
    // long as this function is being called within a delay of 300 ms.
    // This is to avoid a very large number of Apex method calls.
    window.clearTimeout(this.delayTimeout);
    const searchTerm = event.target.value;
    this.delayTimeout = setTimeout(() => {
        this.searchTerm = searchTerm;
    }, 300);
}
2
  • The OP already cleared the previous timeout, so this isn't debounce. It's the "this" aspect that's causing the issue.
    – Phil W
    Mar 5, 2021 at 15:10
  • Thank you for your answer Anudeep, unfortunately that did not help. Mar 5, 2021 at 15:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .