Issue using Stimulus and Htmx

I’m trying to add Stimulus to a Go app that is running with Htmx to add some frontend logic that is very hard to implement with Htmx properly. The application works fine with Htmx but when I add Stimulus, the frontend starts to misbehave.

I have the following input in a form. When this input changes I do a request to validate the email and then I replace a parent div that has this input field and a label. This works perfectly fine with Htmx.

<input
	id="email"
	type="email"
	name="email"
	hx-get="/sign-in/email"
	hx-trigger="change"
	hx-target="#formEmail"
	hx-swap="outerHTML"
	hx-indicator="#formEmailIndicator"
	hx-select-oob="#formSubmitButton"
	value={ state.Email }
	class={ emailInputClass(state.EmailError == "") }
	placeholder="name@company.com"
/>

Then I added the following Stimulus controller:

<script type="module">
	Stimulus.register("sign-in-form", class extends StimulusController {
		submit() {
			let element = this.element.querySelector('input[type="email"]')
			element.disabled = true
			element.readonly = true
		}
	})	
</script>

And the attribute data-action="change->sign-in-form#submit" at the input field. With these changes, the input get disabled during the request, the only issue is, from this moment on the input field value is not send to the server anymore.

Both Htmx and Stimulus are watching the same event, and both are being triggered correctly because Htmx does the request and Stimulus disable the field, the only mystery is the value that vanishes when using Stimulus.

Can anyone explain this?

Using querySelector inside a Stimulus controller is not exactly an anti-pattern, but it is something that is not recommended. You might try adding a data-stimulus-controller attribute to the element you want to observe/change, and then register that in your controller and use it. That way you are sure to hook into the correct element after it has been properly captured by the controller.

Since you want to affect the form on submit, you would add the controller to the form with a data-controller attribute, and then on the email input), add a data-[controller-name]-target attribute to name it. That way you can reference it directly in your controller without any process or drama or timing issues.

<!-- in your html -->
... data-sign-in-form-target="email" ...

// in your controller
  static targets = ['email'];
...
  submit() {
    const email = this.emailTarget;
    email.disabled = true;
    email.readonly = true;
  }

Something like that.

Walter

1 Like