A Tour of Stimulus JS
Tags: javascript, rails, ruby, webpack
Published: 2020-07-23 13:07:30

---
title: A Tour of Stimulus JS
published: true
date: 2020-05-22 13:32:00 UTC
tags: javascript, rails, ruby, webpack
canonical_url: https://web-crunch.com/posts/a-tour-of-stimulus-js
---

Today, I'm excited to walk through a great JavaScript framework that has become popular in the Ruby on Rails community called [Stimulus.js](https://stimulusjs.org/).

{% youtube kCMB3-NHip0 %}

### Not another JavaScript framework

Yes, I said framework. Rest assured, it's not as crazy as many you hear about these days. Stimulus.js stems from the [Basecamp](https://basecamp.com) team. I have a hunch that this framework was introduced to help build their new app called [HEY](https://hey.com) which is due out June 2020.

## What is Stimulus.js?

Think of Stimulus as a way to introduce JavaScript to your website or application in a more modular and reusable way. You keep your existing HTML/CSS code and add Stimulus logic where it makes sense. The framework isn't meant to power your entire front-end. [React.js](https://reactjs.org/) and [Vue.js](https://vuejs.org) for example, have been known to do something like this.

With sprinkles of JavaScript within your website or app code, you can take advantage of the server-side combined with the interactivity of modern JavaScript. To me, that's a win-win.

### Core concepts

Stimulus.js is consists of three main concepts:

- Controllers
- Actions
- Targets

Through modern JavaScript, Stimulus.js scans your pre-existing markup for `controllers` and enables functionality inside. By using data attributes with a convention-driven naming scheme Stimulus.js knows what to look for and how to handle the properties, you author.

A basic example from the documentation looks like this:

The `HTML` markup:

```html
<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 the accompanying JavaScript

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

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

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

```

Let's break things down:

### Controllers

Notice the `data-controller="hello"` declaration on a containing `div` element. This `div` acts as the _wrapper_ around all the controller logic within `hello_controller.js`. If the controller data attribute isn't added to the `div`, the JavaScript never initializes. You can add multiple controllers to an element if needed.

So you might have markup that looks extended like this:

```html
<div data-controller="hello search">
 <!-- Additional markup -->
</div>

```

The name of the JavaScript file is `hello_controller.js`. This is an important convention that Stimulus.js requires.

You give your controller a name, `hello` in this case, and append `_controller.js` to get things working. The `hello` name maps the `data-controller="hello"` attribute by design.

A JavaScript file combined with a `data-controller="controllerName"` attribute is necessary to initialize any JavaScript code with Stimulus.js.

### Targets

Within the context of the `data-controller="hello"` div we have another data attribute called `data-target="hello.name"`. Think of this as the thing you'd "query" for in traditional JavaScript.

Stimulus.js handles the querying by default with its concept of targets.

Targets are namespaced with dot notation by the parent level controller name. Adding a new target anywhere would need the `data-target="hello.myTargetName"` convention enforced. Like controllers, you can have more than one target on an element.

Referencing a target(s) in the JavaScript file happens in a conventional way.

The line below is where you add any targets you've already added to your markup.

```javascript
// hello_controller.js

export default class extends Controller {
  // Defined targets scan the conrtoller HTML for
  // data-target="hello.name" or data-target="hello.output"
  static targets = ["name", "output"] 

}

```

Once defined you can reference them dynamically.

```javascript
this.outputTarget // Single element (i.e. document.querySelector('.think'))
this.outputTargets // All name targets (i.e. document.querySelectorAll('.thing'))
this.hasOutputTarget // returns true or false whether there is a matching target

```

You get this functionality for free with Stimulus which is one of my favorite aspects. No longer do you really need to define variables for setup. The naming convention here is strict by design. You'll append the name you gave your target with the word `target` or `targets` for every new Stimulus.js controller you create.

Actually puting targets to use looks like this:

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

```

The code above queries for the `outputTarget`. Under the hood, it's basically doing the `document.querySelector` work. Then, you can code at will with traditional JavaScript. Here we are setting the `textContent` of the output target to match what's inside the `nameTarget` value input element.

Functions within a Stimulus.js controller are called `actions`. Let's talk about those next.

### Actions

Think of actions as a way to hook into any JavaScript event on an element. The most common event used is probably a `click` event. Looking back at our markup we see another data attribute named `data-action="click->hello#greet"`.

There are a number of conventions to unpack here. The first being the `click->` text. Here's we're signaling to our Stimulus.js controller that we need to listen for a click event. Following the `click->` text is the controller name `hello`. This namespaces the logic being applied to the specific controller JavaScript file `hello_controller.js`. Finally the `#greet` text represents the action itself inside the `hello_controller.js` file. Stimulus.js will fire whatever is inside the function called `greet` within the `hello_controller.js` file only when clicked.

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

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

    // Our action `greet` is fired as a result of the `data-action="click->hello#greet"` code within the markup
  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

```

Combining controllers, targets, and actions get you a fully modular pattern for working with JavaScript. This removes the unnecessary setup and sometimes spaghetti-like code traditional JavaScript is known for.

Additionally, inside any action you can pass the event.

```javascript
greet(event) {
  event.preventDefault()
}

```

### Bonus: Data Maps

Adding additional custom data attributes to your controller code might be necessary as your logic starts to require it. At the parent controller level you can declare new data attributes for use within your controllers.

This might look like the following:

```html
<div data-controller="toggle" data-toggle-open="Toggle open" data-toggle-close="Toggle close">
    <button data-target="toggle.button">Toggle open</button>
    <div data-target="toggle.toggleable" class="hidden">Some content goes here...</div>
</div>

```

Inside the controller, you can access these with a handy `this.data` object

```javascript
// controllers/toggle_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
    static targets = ["toggleable", "button"]

  toggle() {
    if (this.toggleableTarget.classList.contains('hidden')) {
      this.buttonTarget.textContent = this.data.get('open')
    } else {
      this.buttonTarget.textContent = this.data.get('close')
    }
  }
}

```

On top of `this.data.get(key)` you can use `this.data.has(key)`, `this.data.set(key, value)`, and `this.data.delete(key)`,

- `this.data.get(key)` - Returns the string value of the mapped data attribute
- `this.data.has(key)` - Returns true if the mapped data attribute exists
- `this.data.set(key, value)` - Sets the string value of the mapped data attribute
- `this.data.delete(key)` - Deletes the mapped data attribute

### There's more to unpack

I'll finish off by saying this isn't a comprehensive guide. I think the documentation does a better job than I have here but I wanted to maybe introduce you to something different you might have not considered before. Stimulus.js plays very well with Ruby on Rails apps (especially those that use Turbolinks). I find it a very productive way to write JavaScript even though it is a bit opinionated. Rails are the same way which is why they work so well together. There is also the concept of controllers and actions within a Rails app that rings true in Stimulus.js.

If you would like to learn more about Stimulus.js or see it in use let me know in the comments. I'm happy to put it through the paces to better learn it myself!

#### Shameless plug

[![](https://f001.backblazeb2.com/file/hellorails/hr-promo.jpg)](http://hellorails.io)

I have a new course called Hello Rails. Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. If you're a novice when it comes to Ruby or Ruby on Rails I invite you to check out the site. The course will be much like these builds but a super more in-depth version with more realistic goals and deliverables. Download your copy today!