Which is preferable and why: onchange or template.querySelector

javascriptlightning-web-componentsonchange

There are a few different ways to get the current value of a field in LWC but which one is preferable and why?

For example, if I have an lightning input field called 'Footballer Name' on a LWC component, there are at least two ways I can get this in the .js file:

One is by leveraging the onchange specification like so (more info: https://salesforcediaries.com/2020/02/27/how-to-get-field-value-of-lightning-input-in-lightning-web-component-using-onchange-event/):

<lightning-input-field class="requiredInput" field-name="Footballer_Name__c" onchange={handleFootballerNameChange}>

and then in the .js file have:

handleFootballerNameChange(event) {
        console.log('@@@ handleFootballerNameChange has been called');
        this.formObject[event.currentTarget.fieldName] = event.target.value;
        this.footballerName = event.detail.value;
    }

Or we leverage the querySelector method like:

<lightning-input-field class="requiredInput" field-name="Footballer_Name__c" data-field="footballerName">

and then the .js file would look like:

let newValueOfFootballerName = this.template.querySelector("[data-field='footballerName']").value;

I notice that several repos from Salesforce have both versions and I can't find any documentation that suggests that one way is preferable over the other although it seems the former is more conventional.

I suppose a downside of using querySelector is that your HTML file will have lots of different Ids and that could become messy, especially if the field API name changes too as then you ideally would need to try and make sure the css Ids were named in a similar fashion.

I did wonder if querySelector is more performant as it doesn't raise an event but that seems a bit moot.

Best Answer

I did wonder if querySelector is more performant as it doesn't raise an event but that seems a bit moot.

The onchange event is always raised, you simply don't handle it in the querySelector example. Indeed usually you're going to handle events raised by a standard component/tag.

The overhead of a simple handler like the one you used is negligible and might simplify the logic behind the submit button that usually follow a list of input element. Moreover if you handle it you don't need the querySelector, whose performance depends upon how many node there are in the shadow tree of your component:

  • via event.target.value you're just accessing object's properties -> O(1)
  • via this.template.querySelector you're searching an element in a tree structure -> O(n)

Finally leveraging the onchange is mandatory in several cases and most of them requires also debouncing.
I.E. you have an unique field and want to show an error message as soon as the user input a duplicate value instead of waiting the save/update action. In this case you must handle onchange event and call an apex method, but you don't want to call it for every character typed by the user, you would like to call it after the user types the last character, that's why you're debouncing that method.
Another requirement may be:

  • if the user types "foo" show XXX, if they type "bar" show YYY.
  • enable/disable a button if user filled at least K field.

Usually debouncing is implemented as:

timeoutID;

handleChangeField(event) {
    const fieldValue = event.detail.value;
    clearTimeout(this.timeoutID); // will not throw an error if timeoutID is null
    this.timeoutID = setTimeout(() => {
        // logic about fieldValue goes here
        // i.e. call an apex method or raise an event 
    }, DELAY);
}

That's why in almost every scenario I ended up handling the onchange event.