- Replace stimulus imports
- Replace turbolinks namespaces with turbo
- Disable remote forms
-
Render form errors
- Forms on get routes
- Forms in modals
- Replace .js.erb to turbo_stream.erb
With the upcoming Rails 7 release, hotwire and stimulus will be the default JavaScript set-up for any new Rails project. To get existing rails application up to speed with the newest ste-up, the guide below can help you migrate from turbolinks to hotwire.
A note on Devise and Rails UJS
To use devise with hotwire, there are a few changes needed. Check the GoRails episode for a practical guide.
For now, Rails UJS can still co-exist with Hotwire. The guide below is built with the assumption Rails UJS is installed. If you look to say goodbye to UJS completely, check this Drifting Ruby episode.
Installation
Install the gem hotwire-rails. The gem is a wrapper which will install:
- Turbo (@hotwired/turbo-rails")
- Stimulus JS library (@hotwired/stimulus)
If you have already installed stimulus, remove the package using yarn or npm. Ex: yarn remove stimulus@^2.0.0
.
Replace
# old
gem 'turbolinks'
# new
gem 'hotwire-rails'
Run ./bin/bundle install
Run ./bin/rails hotwire:install
Remove any turbolinks reference in application.js
// old
require("turbolinks").start()
// new
import "@hotwired/turbo-rails"
1. Replace stimulus imports
If you already had Stimulus JS installed, you'll need to update your existing stimulus controllers. Set the import library to @hotwired/stimulus
.
// old
import { Controller } from "stimulus"
//new
import { Controller } from "@hotwired/stimulus"
2. Replace turbolinks namespaces with turbo
Check if your app contains turbolinks references and it swap for the new turbo namespace.
For example:
- script tags
// old
document.addEventListener('turbolinks:load', ...)
// new
document.addEventListener('turbo:load', ...)
- turbolinks opt-outs for specific link clicks
<!-- old -->
<%= link_to path, data:{turbolinks: false} do %>
<!-- new -->
<%= link_to path, data:{turbo: false} do %>
- programatically redirect Turbolinks.
# old
Turbolinks.visit(location)
# new
Turbo.visit(location)
3. Disable remote forms
Set the config in application.rb
as mentioned in the turbo-rails to disable all remote forms.
config.action_view.form_with_generates_remote_forms = false
Alternatively, you can change every form, one by one, by adding local: true
.
<!-- old -->
<%= form_with model: @task, url: task_path(@task), method: :put do %>
...
<% end %>
<!-- new -->
<%= form_with model: @task, url: task_path(@task), method: :put, local: true do %>
...
<% end %>
4. Render form errors
4.1. Forms in GET routes
like on /new
or /edit
page
Add status: unprocessable_entity
when rendering the form in the else-leg to render errors on the page.
# old
def create
@task = Task.new(task_params)
if @task.save
redirect_to tasks_path, notice: 'Task created.'
else
render :new
end
end
# new
def create
@task = Task.new(task_params)
if @task.save
redirect_to tasks_path, notice: 'Task created.'
else
render :new, status: :unprocessable_entity
end
end
4.2 Forms in modal
Using the :unprocessable_entity
approach in step 3.1 won't work for forms displayed in modals. This GoRails episode details an in-depth solution. Use the new turbo_stream format to render errors instead of status: :unprocessable_entity
. The turbo_stream.erb will find the form id and replace it with a new HTML template.
This is also a good time to replace existing js.erb
templates to the new turbo_stream format.
# old
def create
@task = Task.new(task_params)
if @task.save
redirect_to tasks_path, notice: 'Task created.'
else
render :new
end
end
# new
def create
@task = Task.new(task_params)
if @task.save
redirect_to tasks_path, notice: 'Task created.'
else
respond_to { |format| format.turbo_stream }
end
end
Next, add an id to the modal form. Read about the dom_id helper here.
<!-- old -->
<%= form_with model: @task) do |f| %>
...
<% end %>
<!-- new -->
<%= form_with model: @task, id: dom_id(@task) do |f| %>
...
<% end %>
Finally, add a new file in the view folder that matches the action name of the controller: create.turbo_stream.erb
<%= turbo_stream.replace "new_task", partial: "tasks/form", task: @task %>
-
new_task
refers to the id of the form that is generated by dom_id(@task). -
"tasks/form"
is the same form partial that initially displays the form in the modal -
@task
is the updated instance with errors which is passed along to the form
5. Replace .js.erb to turbo_stream.erb
Using hotwire and its alternative approach to update content on your page. You want to benefit from the new turbo_stream approach.
Thanks for reading.
Questions? Feedback? Let me know :)
Top comments (2)
Nice article, thanks for sharing.
Thanks!