This articles was originally published on Rails Designer
Stimulus' values API has a great change callback function. This allows you to respond whenever a value changes. 🦉
For a quick refresher: the values API allows you read (and write!) data attributes to the controller's element.
You define them like so in the HTML: <div data-controller="counter" data-counter-time-value="10">10</div>
.
Then in the controller define them in the static values
:
export default class extends Controller {
static values = { time: Number }
}
With above controller you might expect a connect function to start the timer.
// app/javascripts/controllers/counter_controller.js
export default class extends Controller {
static values = { time: Number }
connect() {
this.timer = setInterval(() => {
this.timeValue--;
if (this.timeValue > 0) {
this.element.textContent = this.timeValue;
} else {
this.element.textContent = "Time's up!"
clearInterval(this.timer);
}
}, 1000);
}
}
And above might look just fine to you. But I like to keep my functions smaller in scope, and I am pretty sure the next requirement is to add a stop and pause feature (and I need a bridge to show how the changes callbacks can be used).
So let's do a little refactor using Change Callbacks.
export default class extends Controller {
static values = { time: Number }
connect() {
this.timer = null;
this.#start();
}
// private
timeValueChanged() {
if (this.timeValue > 0) {
this.element.textContent = this.timeValue;
} else {
this.element.textContent = "Time's up!";
clearInterval(this.timer);
}
}
#start() {
this.timer = setInterval(() => {
this.timeValue--;
}, 1000);
}
}
timevalueChanged()
is called whenever the this.timeValue
is changed (which is done in #start()
.
Is this better? “The class has gotten bigger!” But I'd argue it is better. Yes, significantly more lines of code, but to me it's easier to follow. The #start
function is only concerned with starting the timer. You can imagine there being a public function, alongside a stop()
and pause()
function.
- show a maximum allowed character count;
- fetch API endpoint when urlValue changes;
- show the selected option from a select-element;
- disable a button on "loading" value.
It is one of those, somewhat, hidden helper functions in Stimulus that if you know it, you see ways to use it in many situations.