DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

2

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.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (1)

Collapse
 
railsdesigner profile image
Rails Designer

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

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay