20
.
08
.
2025
20
.
08
.
2025
Hotwire
Ruby on Rails
Frontend

Stimulus: Managing State with Outlets and Events

Grzegorz Płóciniak

The beauty of Stimulus lies in its simplicity. You start by sprinkling in isolated components for bits of interactivity, and it feels like magic. But as your application grows, a new challenge emerges: what happens when those isolated components need to talk to each other? In this article, we'll cross that bridge and explore the powerful patterns Stimulus provides for managing state between components.

Let's examine the simplified financial trading application.

The most important element is the chart with price candles accompanied by a vertical price scale along the Y-axis. Each individual candlestick on this chart represents a distinct data point and is managed by its own Stimulus controller.

To draw a price candle correctly on a chart, we need to know where to put it and how tall to make it. In order to do that we need two things:

  • The candle's own data: Its open, high, low, and close prices (OHLC).
  • The chart's overall view: The highest and lowest prices currently shown on the screen.

While the backend service provides the four OHLC values for each candle, we also need to retrieve the broader price context held by other controllers. We will use outlets to create a direct connection between them to accomplish this.

Declare the Outlet in HTML

Outlet is a declared reference to another controller’s element, letting you directly access that controller’s instance and its DOM element. First, you need to set it up on the element of the candle controller.

This is done using a special data attribute that follows the pattern data-[controller]-[outletName]-outlet. The value of this attribute must be a CSS selector that points to the element of the controller you want to connect to. In this case, the candle controller needs an outlet to the scale controller.

<turbo-frame data-controller="candle" data-candle-scale-outlet="#scale">
  ...
</turbo-frame>

Define the Outlet in JavaScript

Next, you must explicitly define the outlet's name in the controller's JavaScript file using the static outlets array. This tells Stimulus to look for the corresponding data- attribute in the HTML.

// In candle_controller.js
static outlets = [ "scale" ]

Access the Connected Controller

With both pieces in place, Stimulus automatically creates a property on your controller instance named this.[outletName]Outlet. You can now access the connected controller's instance, including all of its properties and methods.

// In candle_controller.js
connect() {
  // Access properties from the scale controller via the outlet
  this.drawCandle(this.scaleOutlet.highestValue, this.scaleOutlet.lowestValue);
}

It works until it doesn’t

Initially, each candle is drawn correctly within the visible price scale. However, a problem arises if a candle's price moves outside of this range. When that happens, a complete redraw is necessary, affecting both the price scale itself and every candle on the chart. To efficiently notify other controllers of this scale change, we dispatch an event.

Dispatch an Event from the candle

First, we need each candle controller to announce its presence and its data when it connects. We can achieve this by dispatching a custom event from its connect() method. The event will carry the candle's OHLC data in its detail payload.

// In candle_controller.js
connect() {
  this.drawCandle(this.scaleOutlet.highestValue, this.scaleOutlet.lowestValue);

  // Announce that a new candle was created
  this.dispatch("created", {
    // The event will be named "candle:created"
    detail: {
      open: this.openValue,
      high: this.highValue,
      low: this.lowValue,
      close: this.closeValue
    }
  });
}

Listen for the Event in the scale

Next, the scale controller needs to listen for this candle:created event. Since the candle and scale elements may not have a direct parent-child relationship, we listen on the window to catch the event no matter where it was dispatched from.

<div id="scale" data-controller="scale" data-action="candle:created@window->scale#newCandle">
  ...
</div>

Handle the Event and Respond

Inside the scale controller, the newCandle action will handle the incoming event.

// In scale_controller.js
newCandle({ detail: { open, high, low, close } }) {
  // Logic to check if the new candle's prices are outside the current scale...
}

With these building blocks, the scale controller can calculate new values for its highest and lowest points. Then, to complete the cycle, it must send this new information back to all the candle controllers by dispatching its own event, such as scale:updated.

As a result, the scale is recalculated, and all candle controllers (which would be listening for scale:updated) are redrawn in sync with the new dimensions, keeping the entire chart consistent.

To see how this application works in more detail, the full source code is available on GitHub: https://github.com/visualitypl/trader-simulator

Summary

Your main tool for cross-controller communication should be events. This approach is more expandable and keeps your controllers decoupled. An event is like making a public announcement - the speaker doesn't need to know who is listening; they just broadcast the message.

Outlets are perfect when one controller needs to directly command or read from another specific, known controller. Think of it as having a direct phone number. This creates a clear, one-to-one relationship.

Grzegorz Płóciniak

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

Stimulus: Managing State with Outlets and Events

20
.
08
.
2025
Grzegorz Płóciniak
Hotwire
Ruby on Rails
Frontend
Tradeoffs of Anonymising Production Data by Michał Łęcicki

Tradeoffs of Anonymising Production Data

11
.
06
.
2025
Michał Łęcicki
Postgresql
Software
Ruby MCP Client in Rails by Paweł Strzałkowski

MCP Client in Rails using ruby-mcp-client gem

11
.
06
.
2025
Paweł Strzałkowski
LLM
Ruby on Rails
Actionmcp in Ruby on Rails by Paweł Strzałkowski

MCP Server with Rails and ActionMCP

11
.
06
.
2025
Paweł Strzałkowski
LLM
Ruby on Rails
Banner - MCP Server with FastMCP and Rails by Paweł Strzałkowski

MCP Server with Rails and FastMCP

11
.
06
.
2025
Paweł Strzałkowski
LLM
Ruby
Ruby on Rails

Ruby on Rails and Model Context Protocol

11
.
06
.
2025
Paweł Strzałkowski
Ruby on Rails
LLM
Title image

Highlights from wroclove.rb 2025

24
.
07
.
2025
Kaja Witek
Conferences
Ruby
Jarosław Kowalewski - Migration from Heroku using Kamal

Migration from Heroku using Kamal

11
.
06
.
2025
Jarosław Kowalewski
Backend
store-vs-store_accessor by Michał Łęcicki

Active Record - store vs store_accessor

11
.
06
.
2025
Michał Łęcicki
Ruby
Ruby on Rails
How to become a Ruby Certified Programmer Title image

How to become a Ruby Certified Programmer

11
.
06
.
2025
Michał Łęcicki
Ruby
Visuality
Vector Search in Ruby - Paweł Strzałkowski

Vector Search in Ruby

11
.
06
.
2025
Paweł Strzałkowski
ChatGPT
Embeddings
Postgresql
Ruby
Ruby on Rails
LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

11
.
06
.
2025
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

11
.
06
.
2025
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

24
.
07
.
2025
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

11
.
06
.
2025
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

11
.
06
.
2025
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

11
.
06
.
2025
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

11
.
06
.
2025
Cezary Kłos
Backend
Ruby on Rails

Smooth Concurrent Updates with Hotwire Stimulus

11
.
06
.
2025
Michał Łęcicki
Hotwire
Ruby on Rails
Software
Tutorial

Freelancers vs Software house

11
.
06
.
2025
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

11
.
06
.
2025
Jarosław Kowalewski
Backend
Postgresql
Ruby on Rails

N+1 in Ruby on Rails

11
.
06
.
2025
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend