Introduction

When I first came across Stimulus, I was excited. The premise is great, especially when coupling with a rails app. Convention over configuration, right? No more jQuery code sprinkled around throughout the page and watching for load/change events to perform changes. Not to mention the quirks that come with integrating Turbolinks to your app.

Why not (just?) React?

React is great! I am an avid React developer and want to use React for everything. But, does hiding a few elements in a form or reacting to a few clicks really warrants integrating a monolith like React?

Why not just jQuery?

OK, you made your point. But I will use just jQuery then. jQuery is nice and you are probably already including it in your project because one of the libraries that you want requires it. You don’t need jQuery, but it makes your life simpler and so you use it, right? For hiding showing a few elements, it works fine. But, does it really look clean? You want your code to be maintainable in the future and breaking it into separate concerns allows you to do that. It would also allow you to reuse those concerns in other parts of the app.

Stimulus

Stimulus revolves around three main concepts: Controllers, actions, and targets.

It’s designed to read as a progressive enhancement when you look at the HTML it’s addressing. Such that you can look at a single template and know which behavior is acting upon it. Here’s an example:

<div data-controller="clipboard">
  PIN: <input data-target="clipboard.source" type="text" value="1234" readonly>
  <button data-action="clipboard#copy">Copy to Clipboard</button>
</div>

You can read that and have a pretty good idea of what’s going on. Even without knowing anything about Stimulus or looking at the controller code itself. It’s almost like pseudocode. That’s very different from reading a slice of HTML that has an external JavaScript file apply event handlers to it. It also maintains the separation of concerns that have been lost in many contemporary JavaScript frameworks.

Performance

Here’s an excerpt from their documentation:

Stimulus continuously watches the page, kicking in as soon as magic attributes appear or disappear. It works with any update to the DOM, regardless of whether it comes from a full page load, a Turbolinks page change, or an Ajax request. Stimulus manages the whole lifecycle.

Wait, it watches the whole DOM for every change. That can’t be good! Wrong. It uses the MutationObserver API which is very efficient. If you are observing a node for changes, your callback will not be fired until the DOM has finished changing. When the callback is triggered, it is supplied a list of the changes to the DOM, which you can then loop through and choose to react to.

Usage

Just sprinkle the HTML with the required attributes

<div data-controller="hello">
  <input data-target="hello.name" type="text">

  <button data-action="click->hello#greet">Greet</button>

  <span data-target="hello.output"></span>
</div>

And write a compatible controller

// hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

You can even nest the controllers. To access a child controller from the parent, first set a target to the controller’s root element in the DOM and use this.application.getControllerForElementAndIdentifier(this.childTarget, 'child'); inside your parent controller.