Stimulus: intended use / contrasting with other frameworks

Created on 4 Jan 2018  路  1Comment  路  Source: hotwired/stimulus

Hey folks,

Hopefully this is the right place for this sort of question, if not I'm happy to move it.

I've been playing around with Stimulus recently and wanted to see if the way I'm thinking of using it is by design.

Controllers != Components

So in most component-based frameworks, if I want to change part of the page that is being managed by some component, then I need to do that within the component hierarchy. One of the attractive things about Stimulus seems to be that you can achieve the same thing by just setting up a new Controller for that part of the page.

Concrete example:

Components

I have a component that renders a form and validates it on submit.

In React this might look something like:

<Form>
  <input type="text" name="myField" onChange={ this.onChange } />
  <button type="submit" onClick={ this.onSubmit }>Save</button>
</Form>

This form and a few others in my app need to format a field as a phone number. So this might be accomplished by extracting a special component for phone number fields

<Form>
  <PhoneInput onChange={ this.onChange }></PhoneInput>
  <button type="submit" onClick={ this.onSubmit }>Save</button>
</Form>

Controllers

By contrast, with Stimulus it appears that you would be able to simply add the new behavior as a Controller that would be used in addition to existing ones on the page. In other words, you could add the formatting behavior without making any changes at all to the file with the validating behavior - even though they would both be working with the same field.

For example:

First step: just form validations

/views/users/_form.html.erb

<%= form_with(model: user, local: true, data: { controller: "form" }) do |form| %>
  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name, id: :user_name, data: { target: "form.required_field" } %>
  </div>

  <div class="field">
    <%= form.label :phone_number %>
    <%= form.text_field :phone_number, id: :user_phone_number, data: { target: "form.required_field" } %>
  </div>

  <div class="actions">
    <%= form.submit data: { action: "form#validate" } %>
  </div>
<% end %>

/src/controllers/form_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  validate(event) {
    this.validateRequiredFields()

    if (!this.valid) {
      event.preventDefault()
    }
  }

  private

  get valid() {
    return this.data.valid
  }

  set valid(value) {
    this.data.valid = value
  }

  validateRequiredFields() {
    let anyEmpty = false
    this.targets.findAll("required_field").forEach((element) => {
      let isEmpty = element.value === ""
      element.parentElement.classList.toggle("field_with_errors", isEmpty)
      element.parentElement.classList.toggle("field", !isEmpty)

      if (isEmpty) {
        anyEmpty = isEmpty
      }
    })
    this.valid = !anyEmpty
  }
}

Then if I want to add phone formatting, I can do so without changing that Controller that is handling form validations.

I just add a new controller to the specific part of the page where I need it:

/views/users/_form.html.erb

  <div class="field" data-controller="format">
    <%= form.label :phone_number %>
    <%= form.text_field :phone_number, id: :user_phone_number, data: { target: "form.required_field", action: "format#phoneNumber" } %>
  </div>

/src/controllers/format_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  phoneNumber(_event, target) {
    target.value = target.value.split(" ").join("-")
  }
}

Conclusion

Does this sound right?

In practice, would you expect to see multiple controllers handling different types of behaviors on a page or do you tend to organize things differently?

Most helpful comment

Does this sound right?

Definitely. It's perfectly reasonable to connect multiple controllers to the same element. We describe this pattern of usage almost exactly in the handbook:

Stimulus works by continuously monitoring the page, waiting for the magic data-controller attribute to appear. Like the class attribute, you can put more than one value inside it. But instead of applying or removing CSS class names, data-controller values connect and disconnect Stimulus controllers.

Think of it like this: in the same way that class is a bridge connecting HTML to CSS, data-controller is a bridge from HTML to JavaScript.

Great question!

>All comments

Does this sound right?

Definitely. It's perfectly reasonable to connect multiple controllers to the same element. We describe this pattern of usage almost exactly in the handbook:

Stimulus works by continuously monitoring the page, waiting for the magic data-controller attribute to appear. Like the class attribute, you can put more than one value inside it. But instead of applying or removing CSS class names, data-controller values connect and disconnect Stimulus controllers.

Think of it like this: in the same way that class is a bridge connecting HTML to CSS, data-controller is a bridge from HTML to JavaScript.

Great question!

Was this page helpful?
0 / 5 - 0 ratings