Hotwired

September 24, 2021

Stimulus 3

The third major release of Stimulus represents a big milestone for the Hotwire community. This is the first release where every substantial new feature came as a contribution from outside of Basecamp and HEY. We've got a new package, action parameters, default values, target lifecycle callbacks, debug mode, utility-CSS support, and Controller.shouldLoad. Let's dig into what's new!

New Package
First, Stimulus has moved from stimulus to @hotwired/stimulus on npm. The old package is still available as a proxy, but you should upgrade all your references, if you can.

Also, the Webpack helpers are now longer part of the main distribution. They now live separately under @hotwired/stimulus-webpack-helpers.

Action Parameters
Often you need to invoke the same controller action from multiple places in your application. Before, if you want to specialize those invocations with arguments, you had to use the dataset on the submitter element, coming up with your own convention on naming and scoping. Now action parameters are built into Stimulus. They work like this:

<div data-controller="item">
  <button data-action="item#upvote" 
    data-item-id-param="12345" 
    data-item-url-param="/votes"
    data-item-payload-param='{"value":"1234567"}' 
    data-item-active-param="true">…</button>
</div>

class ItemController extends Controller {
  upvote({ params }) {
    // { id: 12345, url: "/votes", active: true, payload: { value: 1234567 } }
    console.log(params)
  }
}

Read more about Action Parameters in the Reference Handbook. Many thanks to Adrien Poly for this contribution!

Default Values
Ever wanted to have a default value set when a controller provides optional configuration points? Now you can with the new extended API for values:

export default class extends Controller {
  static values = {
    url: { type: String, default: '/bill' },
    interval: { type: Number, default: 5 },
    clicked: Boolean
  }
}

Many thanks to Marco Roth for this contribution!

Target Lifecycle Callbacks
With the new [target-name]TargetConnected() and [name]TargetDisconnected() callbacks, your controller can be alerted when targets enter or exit the controller scope. 

Here's a basic controller that monitors the number of items in a list in order to set a live DOM attribute that can be used for CSS styling:

export default class extends Controller {
  static targets = [ "item" ]
  static values = { size: Number }

  itemTargetConnected() {
    this.refreshSizeValue()
  }

  itemTargetDisconnected() {
    this.refreshSizeValue()
  }

  refreshSizeValue() {
    this.sizeValue = this.itemTargets.length
  }
}

Read more in the Lifecycle Callbacks part of the Reference Handbook. Many thanks to Sean Doyle for this contribution!

Debug Mode
In debug mode application lifecycles, controller lifecycles and actions events are sent to the Stimulus Logger with a default output to the console. Each log includes context information available in a detailed view. You can either turn on debug mode in your JavaScript bundle or live in the browser console.

Read more in the PR. Many thanks (again) to Adrien Poly for this contribution!

[key]Classes Handles Multiple CSS Classes
Utility-first CSS is becoming increasingly popular as an alternative to BEM (the convention used in the Handbook). We've therefore added a [key]Classes property that maps a space separated list of classes into an array.

Here's how this would work when using Tailwind:

<div data-controller="highlight" 
     data-highlight-pulse-class="animate-pulse ring-8 ring-teal-600 ring-offset-8 rounded">

export default class extends Controller {
  static targets = [ "subject" ]
  static classes = [ "pulse" ]

  pulse() {
    this.subjectTarget.classList.add(...this.pulseClasses)
  }
}

Many thanks to Matt Swanson for this contribution!

Prevent Controllers From Registering With Controller#shouldLoad
Some times you don't want controllers dedicated to certain environments, such as a particular user agent, to be registered, and due to caching concerns, you can't just modify your HTML. That's where the new static Controller.shouldLoad method comes in. If it returns false, your controller will not be registered and loaded when calling application.register on it.

The Future of Hotwire
Together with Turbo, Stimulus is poised for a big boost in adoption, as both are being included as the default JavaScript framework combo in Rails 7. This Hotwired Rails stack will introduce a whole new audience to the wonders of making modern web apps with minimal JavaScript. Add import mapping to the mix, and you have the most potent package for developing amazing new apps with a fraction of the complexity it used to require.

Please help us further develop this exciting new stack. It's well past due that we have a complete alternative to the prevailing wisdom that modern web apps must mean single-page applications sending JSON back and forth between duplicated logic on the client and server.

HTML Over The Wire is the way.