Stencil.js – explanation of event listeners

Consider the following simple input component:

import { Component, VNode, h, Prop, Host } from '@stencil/core'

@Component({
  tag: 'my-input',
})
export class MyInput {
  @Prop() inputId!: string
  @Prop() label!: string
  @Prop() value: string

  render(): VNode {
    return (
      <Host>
        <label htmlFor={this.inputId}>{this.label}</label>
        <input type="text" id={this.inputId} value={this.value}  />
      </Host>
    )
  }
}

Parent component that renders <my-input> component with couple of event listeners:

import { Component, h, VNode } from '@stencil/core'

@Component({
  tag: 'my-page',
})
export class MyPage {
  private handleInput = (ev: Event) => {
    // this works
    console.log('handle input')
  }

  private handleFocus = (ev: Event) => {
    // this doesn't work
    console.log('handle focus')
  }

  private handleClick = (ev: Event) => {
    // this works
    console.log('handle click')
  }

  private handleBlur = (ev: Event) => {
    // this doesn't work
    console.log('handle blur')
  }

  render(): VNode {
    return (
      <my-input
        label="Label"
        inputId="testId"
        onInput={this.handleInput}
        onFocus={this.handleFocus}
        onClick={this.handleClick}
        onBlur={this.handleBlur}
      />
    )
  }
}

Would someone explain to me please, how it’s possible that some of the listeners actually work and the rest do not?

Answer

The reason the focus and blur event handlers don’t fire is because you’re attaching the listeners to the my-input element and not the actual input element. That means only the listeners of events that bubble will fire (click, input) and the rest (focus, blur) won’t.

One way to enable the use of those non-bubbling events is to proxy them:

export class MyInput {
  @Event() myFocus: EventEmitter<FocusEvent>;

  render() {
    return (
      <Host>
        <label htmlFor={this.inputId}>{this.label}</label>
        <input type="text" id={this.inputId} value={this.value} onFocus={e => this.myFocus.emit(e)} />
      </Host>
    )
  }
}

Here I’ve attached an focus event handler to the input and fire the custom myFocus event which you can listen for from outside the component. You might also want to stop these events from bubbling by changing the decorator to @Listen({ bubbles: false }) myFocus;

This is also how the Ionic Framework (made by the same people as Stencil) does it, see https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/input/input.tsx#L220

Leave a Reply

Your email address will not be published. Required fields are marked *