State Management with the Values API
by Julian Rubisch ![](https://github.com/julianrubisch.png?size=24)
A question that often arises is how and where to store state in a Stimulus controller. The correct answer in 90% of all cases is in Stimulus values.
Bad
// map_controller.js
import { Controller } from “@hotwired/stimulus”;
export default class extends Controller {
connect() {
this.map = new Map(this.element); // some map service
this.markers = [];
}
addMarker() {
this.markers.push({…})
this.map.updateMarkers(this.markers);
}
}
// map_controller.js import { Controller } from "@hotwired/stimulus"; export default class extends Controller { connect() { this.map = new Map(this.element); // some map service this.markers = []; } addMarker() { this.markers.push({...}) this.map.updateMarkers(this.markers); } }
Good
// map_controller.js
import { Controller } from “@hotwired/stimulus”;
export default class extends Controller {
static values = {markers: Array}
connect() {
this.map = new Map(this.element); // some map service
}
addMarker() {
this.markersValue.push({…});
}
markersValueChanged(markers) {
this.map.updateMarkers(markers);
}
}
// map_controller.js import { Controller } from "@hotwired/stimulus"; export default class extends Controller { static values = {markers: Array} connect() { this.map = new Map(this.element); // some map service } addMarker() { this.markersValue.push({...}); } markersValueChanged(markers) { this.map.updateMarkers(markers); } }
Rationale
Stimulus values being serialized and stored in the DOM itself provides the single source of truth for the controller in question. This not only enables mutating state from outside (think via Turbo Streams or Turbo Morphs), it also ties in neatly with Turbo Caching, for example.
Contraindications
In some cases, controller state might not be serializable into one of the built-in types. Other times, they might contain sensitive data that you do not want to be exposed in the HTML.