Stimulus 2.0 Value change callbacks. What are they good for?
Tags: stimulus, rails, javascript
Published: 2020-12-09 20:30:47

A few days ago, [Stimulus 2.0](https://github.com/stimulusjs/stimulus/releases/tag/v2.0.0) has finally been released. One of the key new features is the **Values API** that is supposed to replace the original ”Data API“ and become the main means of managing state in Stimulus controllers (besides Targets). The new API is documented in the [Handbook](https://stimulusjs.org/handbook/managing-state#using-values) as well as the [Reference](https://stimulusjs.org/reference/values) and I’ve also [written](https://dev.to/borama/a-few-sneak-peeks-into-hey-com-technology-v-stimulus-enhancements-gea) about this feature before.

One thing about the Values API that I couldn’t wrap my head around were the **[Value changed callbacks](https://stimulusjs.org/handbook/managing-state#change-callbacks)**. These are callback methods in the controllers that are called whenever the DOM `data-` attribute for the corresponding Value changes. I had no problem understanding them technically but couldn’t think of any real−world use case that would substantially benefit from this pattern. But the Basecamp people must have had a good reason to add this, they are _not_ particularly known for frivolously adding new features!

So I gave it a deeper thought and eventually came up with a few usage patterns that make sense to me. So here they are:


## Preventing code repetition

One of the patterns is actually mentioned in the Handbook itself. Stimulus 2 automatically [generates getters and setters](https://stimulusjs.org/reference/values#properties-and-attributes) for all Values defined in the controller. Thanks to setters, we can now write `this.someValue = 1234` and the corresponding `data-` attribute will get automatically updated in the DOM, saving the state for us. 

Now, what if we need to run some JavaScript based on the Value state, perhaps to update the UI accordingly? We could run the code right after each setting of the Value, like this:

```js
// The not very DRY pattern (Stimulus 2)
this.someValue = 1234
this.updateUIDueToSomeValue()
...
this.someValue = 5678
this.updateUIDueToSomeValue()
```

but that results in a lot of repeated code that is easy to mess up.

Note that in Stimulus 1, we had to [write a setter](https://github.com/stimulusjs/stimulus/blob/v1.1.1/docs/handbook/04_managing_state.md#persisting-state-in-the-dom) ourselves so we had a natural place to add such code:

```js
// The deprecated pattern (Stimulus 1)
this.someValue = 1234
...
this.someValue = 5678

set someValue(value) {
  this.data.set("someValue", value)
  this.updateUIDueToSomeValue()
}
```

In Stimulus 2, the setters are already ”baked-in“ outside our code and that’s where the change callbacks come to help: a **[Value change callback](https://stimulusjs.org/reference/values#change-callbacks) is a specially−named method that will be called with every change of the Value**:

```js
// The preferred pattern (Stimulus 2)
this.someValue = 1234
...
this.someValue = 5678

someValueChanged() {    // <-- this is called automatically, twice
  this.updateUIDueToSomeValue()
}
```

Using change callbacks we can get our controllers to a similarly [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) shape as we were able to in Stimulus 1.

### Responding to updates from legacy JavaScript code

This is all nice but change callbacks don’t just listen to Value updates in the controller code. They also trigger upon changes of the corresponding `data-` attributes in the DOM, i.e. they **listen to external updates of the Values**! This feature can be useful in many ways.

Suppose that our Stimulus controller has to interact with a page widget that is otherwise governed by some legacy JavaScript code. Be it some external JS library or a complex custom legacy JS, we’re talking about code that is not possible or easy to rewrite to Stimulus. We don’t need to make this JS code to talk to our controller directly, what we need is to make it update the `data-` attributes in the DOM instead.

There is a [basic JS fiddle](https://jsfiddle.net/zufhka8s/3/) that demonstrates this (it’s a follow-up of a ”currency converter“ I’ve [shown before](https://dev.to/borama/a-few-sneak-peeks-into-hey-com-technology-v-stimulus-enhancements-gea#a-small-refresh-on-stimulus-data-api)). The legacy code is approximated here as a JS timer which, being triggered 5 seconds after the page load, changes the currency rates that the Stimulus controller calculates with. The relevant code snippet for this is the following:

```javascript
// change to new currency rates after 5s
newInsaneRates = {
  ...
}

setTimeout(function() {
  document.getElementById('converter')
          .setAttribute('data-conversion-rates-value', 
                         JSON.stringify(newInsaneRates))
}, 5000)
```

Just try running the fiddle, put a ”price“ in the input and watch the converted prices recalculate automatically after 5s. The trick here is that the timer code does not have to communicate directly with the controller, in fact, it does not even have to know that such a controller exists! All it has to do is **update a `data-` attribute in the DOM** and the controller’s change callback takes care of the rest.

### Responding to asynchronous page updates from back-end

So far we’ve seen the change callbacks triggered by front-end events but this does not have to be the case − with a bit of help, **callbacks can respond equally well to page updates originating from the back-end**.

A notable example is [StimulusReflex](https://docs.stimulusreflex.com/), the framework that uses [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html) web sockets for asynchronous communication between front-end and back-end. Here, the actual means of data transfer between front- and back-end is not that important, more interesting is that StimulusReflex uses [morphdom](https://github.com/patrick-steele-idem/morphdom), an ingenious little library that can transform a part of the current page DOM efficiently into an updated HTML that it takes as input. The new HTML is typically rendered on the back-end and sent over the wire by StimulusReflex to the browser where the morphdom library  _morphs_ it into the current page (without reloading the page). 

This way, the back-end can update a particular `data-` attribute of a Stimulus-controlled element, the attribute is morphed into the current client-side DOM and Stimulus automatically triggers the change callback for the corresponding Value. In effect, **the back-end can control front-end behavior using Stimulus and its change callbacks**.

> By the way, DHH [promises](https://twitter.com/dhh/status/1330936785313132548) a **”New magic“** to be released soon that should bring rich client-side interactivity without having to write a ton of JavaScript (supposedly using a similar concept to StimulusReflex). If [my guess](https://dev.to/borama/a-few-sneak-peeks-into-hey-com-technology-vi-template-page-updates-11if) (based on what we can see on the [hey.com](https://hey.com) site) is right, the new magic will work comparably to StimulusReflex, only perhaps a bit less efficiently, as it probably will not use  morphing but will replace whole updated elements. We’ll see soon!

### Inter−controller communication

Sometimes it is useful to trigger a Stimulus controller action from another controller. Since Stimulus first came out, there have been a number of suggestions how to handle this interaction: triggering [custom events](https://discourse.stimulusjs.org/t/waiting-for-controllers-or-controller-interaction/1092/2), [locating the controller](https://github.com/stimulusjs/stimulus/issues/35#issuecomment-355255358) via its element or [exposing the controller](https://leastbad.com/stimulus-power-move) in its element tag.

Now, it just came to my mind that the Values API (together with change callbacks) could be used for inter-controller communication, too. If any code, inside or outside a Stimulus controller, can affect a `data-` attribute value, it can also trigger behavior of the controller handling that value. Somehow it feels like **triggering a (change) event in the target controller with a value parameter passed in**. 

### Debugging controller behavior

Finally, it might as well be _you_ who alters a controller-governed value, right in the Developer tools console! For a real−world example and if you have a Hey account, just try searching for something in the Imbox, open up the Dev Tools and search through the HTML for the `data-search-selected-index-value` attribute. You can now change its value and observe the effect - the bluish background selection will move among the results according to your input!

![Hey.com Data API change](https://dev-to-uploads.s3.amazonaws.com/i/eng9em554t9mof6jbi6v.png)

### Conclusion

To conclude, Stimulus 2 change callbacks follow the classic Rails pattern of callbacks, this time in the client-side code. They watch updates on an element attribute and act according to its value. As with all callbacks on general, this leads to some indirection in the flow of the front-end code and a developer reading the corresponding HTML must be aware of the Stimulus conventions to know where to look for the related behavior. But hey, this is the Rails way!

_If you’d like to read more stuff like this, follow me here or on [my Twitter](https://twitter.com/boramacz). Thanks!_