How to upgrade/migrate turbolinks to hotwire
Tags: rails, hotwire, turbolinks, tutorial
Published: 2021-09-21 15:59:28


1. [Replace stimulus imports](#1-replace-stimulus-imports)
2. [Replace turbolinks namespaces with turbo](#2-replace-turbolinks-namespaces-with-turbo)
3. [Disable remote forms](#3-disable-remote-forms)
4. [Render form errors](#4-render-form-errors)
    1. Forms on get routes
    2. Forms in modals
5. [Replace .js.erb to turbo_stream.erb](#5-replace-jserb-to-turbostreamerb)

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](https://gorails.com/episodes/devise-hotwire-turbo) for a practical guide. 

For now, [Rails UJS](https://github.com/hotwired/turbo-rails#compatibility-with-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](https://www.driftingruby.com/episodes/hotwire-turbo-replacing-rails-ujs).

## Installation
Install the gem [hotwire-rails](https://github.com/hotwired/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 
```bash
# old
gem 'turbolinks'
# new
gem 'hotwire-rails'
```

Run `./bin/bundle install`
Run `./bin/rails hotwire:install`

Remove any turbolinks reference in `application.js`
```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`.

```js
// 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

```js
// old
document.addEventListener('turbolinks:load', ...)
// new
document.addEventListener('turbo:load', ...)
```

- turbolinks opt-outs for specific link clicks

```erb
<!-- old -->
<%= link_to path, data:{turbolinks: false} do %>
<!-- new -->
<%= link_to path, data:{turbo: false} do %>
```

- programatically redirect Turbolinks.
```ruby
# old
Turbolinks.visit(location)
# new
Turbo.visit(location)
```
---

## 3. Disable remote forms
Set the config in `application.rb` as mentioned in the [turbo-rails](https://github.com/hotwired/turbo-rails/blob/main/UPGRADING.md) to disable all remote forms.
```ruby
config.action_view.form_with_generates_remote_forms = false
```

Alternatively, you can change every form, one by one, by adding `local: true`.

```erb
<!-- 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.

```ruby
# 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](https://gorails.com/episodes/hotwire-modal-forms) 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.

```ruby
# 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](https://apidock.com/rails/ActionView/RecordIdentifier/dom_id).
```erb
<!-- 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`

```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 :)