Quick Start

How to use StimulusReflex in your app

Before you begin...

A great user experience can be created with Rails alone. Tools such as Russian Doll caching, UJS, Stimulus, and Turbolinks are incredibly powerful when combined. Could you build your application using these tools without introducing StimulusReflex?

We are only alive for a short while and learning any new technology is a sacrifice of time spent with those you love, creating art or walking in the woods. ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง๐ŸŽจ๐ŸŒฒ

Every framework you learn is a lost opportunity to build something that could really matter to the world. Please choose responsibly. โณ

It might strike you as odd that we would start by questioning whether you need this library at all. Our motivations are an extension of the question we hope more people will ask.

Instead of "Which Single Page App framework should I use?" we believe that StimulusReflex can empower people to wonder "Do we still need React, given what we now know is possible?" ๐Ÿคฏ

Video Tutorial: Introduction to StimulusReflex

โ€‹Chris from GoRails has released the first of hopefully many tutorial videos demonstrating how to get up and running with StimulusReflex in about ten minutes: โฑ๏ธ๐Ÿ‘

Hello, Reflex World!

There are two ways to enable StimulusReflex in your projects: use the data-reflex attribute to declare a reflex without any code, or call the stimulate method inside of a Stimulus controller. We can use these techniques interchangeably, and both of them trigger a server-side "Reflex action" in response to users interacting with your UI.

Let's dig into it!

Trigger Reflex actions with data-reflex attributes

This example updates the page with the latest count when the link is clicked:

app/views/pages/index.html.erb
<a href="#"
data-reflex="click->CounterReflex#increment"
data-step="1"
data-count="<%= @count.to_i %>"
>Increment <%= @count.to_i %></a>

We use data attributes to declaratively tell StimulusReflex to pay special attention to this anchor link. The data-reflex attribute allows us to map an action on the client to code that will be executed on the server.

The syntax follows Stimulus format: [DOM-event]->[ReflexClass]#[action]

The other two attributes data-step and data-count are used to pass data to the server. You can think of them as arguments.

The syntax requirement for data-reflex was recently loosened to make specifying "Reflex" optional. In the example above, you could now opt to use a shorter form: data-reflex="click->Counter#increment"

If you're watching a video or following a tutorial and see the long-form usage, there is no functional difference between the two - it's just shorter!

app/reflexes/counter_reflex.rb
class CounterReflex < StimulusReflex::Reflex
def increment
@count = element.dataset[:count].to_i + element.dataset[:step].to_i
end
end

StimulusReflex maps your requests to Reflex classes that live in your app/reflexes folder. In this example, the increment action is called and the count is incremented by 1. The @count instance variable is passed to the template when it is re-rendered.

Concerns like managing state and rendering views are handled server side. Instance variables set in the Reflex action can be combined with cached fragments and potentially updated data fetched from ActiveRecord to modify the UI.

The magic is that there is no magic. What the user sees is exactly what they will see if they refresh the page in their browser.

StimulusReflex keeps a 1:1 relationship between application state and what is visible in the browser so that you simply don't have to manage state on the client. This translates to a massive reduction in application complexity and frees you to spend your time on features instead of state synchronization.

If you change the code in a Reflex class, you must refresh the page in your browser to interact with the new version of your code.

Trigger Reflex actions inside Stimulus controllers

Real-world applications will benefit from additional structure and more granular control. Building on the solid foundation that Stimulus provides, we can import StimulusReflex into our Stimulus controllers and build complex functionality.

Let's build on our increment counter example by adding a Stimulus controller and manually triggering a Reflex action by calling the stimulate method.

  1. Declare the appropriate data attributes in HTML.

  2. Create a client side StimulusReflex controller with JavaScript.

  3. Create a server side Reflex object with Ruby.

  4. Create a server side Example controller with Ruby.

app/views/pages/index.html.erb
<a href="#"
data-controller="counter"
data-action="click->counter#increment"
>Increment <%= @count %></a>

Here, we rely on the standard Stimulus data-controller and data-action attributes. There's no StimulusReflex-specific markup required.

app/javascript/controllers/counter_controller.js
import { Controller } from 'stimulus';
import StimulusReflex from 'stimulus_reflex';
โ€‹
export default class extends Controller {
connect() {
StimulusReflex.register(this)
}
โ€‹
increment(event) {
event.preventDefault()
this.stimulate('Counter#increment', 1)
}
}

The controller's connect lifecycle method fires during both the initial browser page load and after Turbolinks visits that refresh content. We tell StimulusReflex that this controller is going to be calling server-side Reflex actions by passing the Stimulus Controller instance to the register method. This gives the controller a stimulate method that we can call.

When the user clicks the anchor, the Stimulus event system calls the increment method on our controller. In this example, we pass two parameters: the first one follows the format [ServerSideClass]#[action] and informs the server which Reflex action in which Reflex class we want to trigger. Our second parameter is an optional argument that is passed to the Reflex action as a parameter.

If you're responding to an event like click on an element that would have a default action (such as an a or a button element) it's very important that you call preventDefault() on that event, or else you will experience undesirable side effects such as page navigation or form submission.

app/reflexes/counter_reflex.rb
class CounterReflex < StimulusReflex::Reflex
def increment(step = 1)
session[:count] = session[:count].to_i + step
end
end

Here, you can see how we accept a step argument to our increment Reflex action. We're also now switching to using the Rails session object to persist our values across multiple page load operations. Note that you can only provide parameters to Reflex actions by calling the stimulate method with arguments; there is no equivalent for Reflexes declared with data attributes.

app/controllers/pages_controller.rb
class PagesController < ApplicationController
def index
@count = session[:count].to_i
end
end

Finally, we set the value of the @count instance variable in the controller action. When the page is first loaded, there will be no session[:count] value and @count will be nil, which converts to an integer as 0... our initial value.

In a typical Rails app, we would set the value of @count after fetching it from a persistent data store such as Postgres or Redis. To keep this example simple, we use Rails' session to store our counter value.

StimulusReflex Generator

We provide a generator that performs a scaffold-like functionality for StimulusReflex. It will generate files and classes appropriate to whether you specify a singular or pluralized name for your reflex class. For example, user and users are both valid and useful in different situations.

bundle exec rails generate stimulus_reflex user

This will create but not overwrite the following files:

  1. app/javascript/controllers/application_controller.js

  2. app/javascript/controllers/user_controller.js

  3. app/reflexes/application_reflex.rb

  4. app/reflexes/user_reflex.rb

If you later destroy a stimulus_reflex "scaffold" using bundle exec rails destroy stimulus_reflex user your application_reflex.rb and application_controller.js will be preserved.