javascriptruby-on-railsstimulusjs

Stimulus controller action fires twice on click


For some reason, my stimulus controller, load once, but the action is fired twice on click.
I can't find where the issue is...

My code is pretty simple though, so I will share it below:

// app/javascript/application.js
https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"

// app/javascript/controllers/application.js
import { Application } from "@hotwired/stimulus"
const application = Application.start()
application.debug = false
window.Stimulus   = application
export { application }
// app/javascript/controllers/index.js
import { application } from "controllers/application"
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
// app/javascript/controllers/radio_button_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static classes = [ 'active', 'inactive', 'invisible' ]

  connect() {
    console.log('connected to ', this.element.querySelector('input').value)
  }

  markAsChecked(event) {
    event.stopPropagation();
    console.log('-', this.element.querySelector('input').value);
  }
}
// view.html.erb

<div class="flex flex-1 gap-x-8 justify-start">
  <label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop">
    <input type="radio" value="public" name="destination[access_type]" id="destination_access_type_public">
  </label>
  <label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop">
    <input type="radio" value="private" name="destination[access_type]" id="destination_access_type_private">
  </label>
  <label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop">
    <input type="radio" value="backend" name="destination[access_type]" id="destination_access_type_backend">
  </label>
</div>

When I run the following commands in the developer console, I also get double action firing

// developer console
temp1 // label
temp1.click()
// - public
// - public
temp2 // input
temp2.click()
// - public

As you can see, markAsChecked is triggered twice when I click on the label, and once when I click on the input. I have no idea why...
(I expect it to always trigger once)


Solution

  • It's the default <label> element behavior:

    When a user clicks or touches/taps a label, the browser passes the focus to its associated input (the resulting event is also raised for the input)...

    https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label

    ...clicking the label in the following snippet could trigger the user agent to fire a click event at the input element, as if the element itself had been triggered by the user:

    <label><input type=checkbox name=lost> Lost</label>
    

    https://html.spec.whatwg.org/multipage/forms.html#the-label-element


    Note that when clicking a <label> you get two events but event.target is different:

    document.addEventListener("click", (event) => {
      console.log(event.target);
    })
    <label for="destination_access_type_public">click me</label>
    <input type="radio" value="public" name="destination[access_type]" id="destination_access_type_public">

    You can add data-action to input to always get a single event:

    <label data-controller="radio-button">
      <input data-action="click->radio-button#markAsChecked" type="radio" value="public" name="destination[access_type]" id="destination_access_type_public">
    </label>