DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

Refactor Stimulus.js Controllers to Use Change Callbacks

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 }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
railsdesigner profile image
Rails Designer

Have you used change callbacks already? If so, what was the use case?