StimulusReflex with Importmaps on Rails 7
Tags: stimulusreflex, rails
Published: 2022-01-07 09:00:45

---
title: StimulusReflex with Importmaps on Rails 7
published: true
date: 2022-01-06 00:00:00 UTC
tags: stimulusreflex,rails
canonical_url: https://blog.minthesize.com/stimulus-reflex-importmaps
---

This short post is intended as a how-to get up and running with StimulusReflex and the new [importmap-rails](https://github.com/rails/importmap-rails) gem to go all transpiler/bundler-less in your asset handling. Since not all generators in StimulusReflex have been updated to reflect this, I will keep this post updated ✌.

For reference, here is the link to the [Github repository](https://github.com/julianrubisch/stimulus_reflex_importmaps) with the source code created in this walkthrough.

If you have any questions meanwhile, don’t hesitate to jump on the [Discord](https://discord.gg/stimulus-reflex) server 💚.

### Prepare Rails

Since Rails 7.0, importmaps, along with Hotwire aka Turbo and Stimulus, is the default frontend stack. Before starting out, we’ll make sure that we’re running the correct Rails version.

```bash
$ rails -v
Rails 7.0.0

$ rails new stimulus_reflex_importmaps
$ cd stimulus_reflex_importmaps
```

This will install and configure `turbo-rails`, `stimulus-rails`, and `importmap-rails`, which are the default starting with Rails 7.

**Note:** If you start from an existing app (e.g. Rails 6.1), you will need to add and install those yourself:

```bash
$ bundle add importmap-rails turbo-rails stimulus-rails
$ bin/rails importmap:install
$ bin/rails turbo:install
$ bin/rails stimulus:install
```

### Set up StimulusReflex

Next, we’re going to add the StimulusReflex gem with using the most recent prerelease.

```bash
$ bundle add stimulus_reflex -v 3.5.0.pre8
```

Since the StimulusReflex installer hasn’t been updated to reflect the latest changes in Rails, we’ll have to do a couple of chores ourselves.

#### 1. Enable Caching

StimulusReflex advises to use the cache store for session persistence, so we’ll configure that in `development.rb`:

```ruby
# config/environments/development.rb
Rails.application.configure do
  # ...
  config.session_store :cache_store
  # ...
end
```

And enable it:

```bash
$ bin/rails dev:cache
```

#### 2. Appease the StimulusReflex Sanity Checker

By default, StimulusReflex’s sanity checker will prevent your Rails process from starting if the Ruby and Javascript versions are not congruent. While the behavior of the checker is reworked at the moment, we will have to work around it for now.

To this end, set up a StimulusReflex initializer to bypass it:

```bash
$ bin/rails generate stimulus_reflex:initializer
```

```ruby
# config/initializers/stimulus_reflex.rb

StimulusReflex.configure do |config|
  config.on_failed_sanity_checks = :warn
end

```

#### 3. Pin the Required Packages

For the importmap approach, we need to use the `bin/importmap` tool to pin the StimulusReflex javascript package as follows:

```bash
$ bin/importmap pin stimulus_reflex@3.5.0-pre8

Pinning "stimulus_reflex" to https://ga.jspm.io/npm:stimulus_reflex@3.5.0-pre8/javascript/stimulus_reflex.js
Pinning "@hotwired/stimulus" to https://ga.jspm.io/npm:@hotwired/stimulus@3.0.1/dist/stimulus.js
Pinning "@rails/actioncable" to https://ga.jspm.io/npm:@rails/actioncable@7.0.0/app/assets/javascripts/actioncable.esm.js
Pinning "cable_ready" to https://ga.jspm.io/npm:cable_ready@5.0.0-pre8/javascript/index.js
Pinning "morphdom" to https://ga.jspm.io/npm:morphdom@2.6.1/dist/morphdom.js
Pinning "stimulus" to https://ga.jspm.io/npm:stimulus@3.0.1/dist/stimulus.js
```

Note that due to the namespace change that Hotwire has undergone, we have now pinned two separate Stimulus packages (`@hotwired/stimulus` and `stimulus`). StimulusReflex still references the legacy glue package `stimulus`, the work to transfer everything cleanly to `@hotwired/stimulus` is still in progress. Since we hand off the `application` to StimulusReflex, it shouldn’t be a problem though (see below).

#### 4. Initialize the StimulusReflex Client

We use the ActionCable consumer obtained from Turbo’s [cable](https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable.js) interface to intialize StimulusReflex with a top level `await`:

```js
// app/javascript/controllers/index.js

import StimulusReflex from "stimulus_reflex"; // <-- add this
import { application } from "./application";
import { cable } from "@hotwired/turbo-rails"; // <-- add this

// Eager load all controllers defined in the import map under controllers/\*\*/\*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
eagerLoadControllersFrom("controllers", application);

// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)

// initialize StimulusReflex w/top-level await
const consumer = await cable.getConsumer()
StimulusReflex.initialize(application, { consumer, debug: true });
```

### Test Everything with a CounterReflex

For the rest of this walkthrough, I follow the official [Quickstart Guide](https://docs.stimulusreflex.com/hello-world/quickstart) closely (for any in-depth explanation of what is going on here, please look there). Let’s first add a `PagesController` with only an `index` route:

```bash
$ bin/rails g controller Pages index
```

```ruby
# config/routes.rb

Rails.application.routes.draw do
  resources :pages, only: :index
end
```

```ruby
# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def index
  end
end
```

Then, we’ll generate a `CounterReflex` with an `increment` action and use it to increase a `@count` index variable derived from a `data-count` attribute on the invoking element:

```bash
$ bin/rails g stimulus_reflex Counter increment
```

```ruby
# app/reflexes/counter_reflex.rb

class CounterReflex < ApplicationReflex
  def increment
    @count = element.dataset[:count].to_i + element.dataset[:step].to_i
  end
end
```

In the `index` view, we’ll furnish a simple `<a>` tag with a `data-reflex` attribute that invokes this `increment` action on click:

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

Now it’s time to start Rails and kick the tires:

```bash
$ bin/rails s
```

[![../assets/images/posts/2022/2022-01-06-importmaps-counter.gif](https://blog.minthesize.com/assets/images/posts/2022/2022-01-06-importmaps-counter.gif)](../assets/images/posts/2022/2022-01-06-importmaps-counter.gif%20)

Success! 🎉

### Testing Client-Side Invocation

Now that we’ve established that the declarative way works, let’s double check that imperative client-side invocation functions, too. Again, nothing new here, refer to the relevant [section in the Quick Start Guide.](https://docs.stimulusreflex.com/hello-world/quickstart#trigger-reflex-actions-inside-stimulus-controllers)

Instead of a `data-reflex`, we are going to reference the Stimulus controller directly, and hook up a `data-action`:

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

Remember that this is now dependent on our Stimulus `counter_controller`, so we have some work to do there:

```js
// app/javascript/controllers/counter_controller.js

import ApplicationController from './application_controller'

export default class extends ApplicationController {
  connect () {
    super.connect()
  }

  increment() {
    this.stimulate('Counter#increment', 1)
  }
}
```

To stay in sync with the Quick Start guide, let’s switch to session storage and passing the `step` via an argument:

```ruby
# app/reflexes/counter_reflex.rb

class CounterReflex < ApplicationReflex
  def increment(step = 1)
    session[:count] = session[:count].to_i + step
  end
end
```

We pick this up in the controller again, to render a new `@count`:

```ruby
# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def index
    @count = session[:count].to_i
  end
end
```

Voilà 🥂

### Conclusion

We’ve established a way to get StimulusReflex up and running with [importmaps](https://github.com/WICG/import-maps). The tooling isn’t there yet, but this post essentially lays out the steps that have to be taken to patch it. I will keep updating it as the installers and generators evolve to become `importmap-rails` compliant.