Lazy Load Form Fields In Rails Using StimulusReflex
Tags: ruby, rails, stimulus, stimulusreflex
Published: 2021-03-20 14:27:19

This pattern can help lazy load form fields in [Rails](https://rubyonrails.org/) using [StimulusReflex](https://docs.stimulusreflex.com/) which can be useful when calling an external API or loading slow relationships. 

![stimulus_reflex_800](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d59yd4qiviz97tbllrh1.png) 
 
For example, let’s say you have a form where you want to pull in a list of GitHub repos for a user so they can display them on their profile.

Loading this data from GitHub’s API can take a couple of seconds depending on the number of repos the user has, so rather than delaying the entire form from loading we can instead load them asynchronously.

The initial `/app/views/users/_form.html.erb` would look something like this.

```erb
<%= form_with(model: user, url: [user], local: true) do |form| -%>
<div class="card">
  <ul class="list-group list-group-flush">
    <li class="list-group-item">
      <%= form.text_field :name, label: "Name", placeholder: "Enter name", required: true %>
    </li>
    <li class="list-group-item">
      <%= form.email_field :email, label: "Email", placeholder: "Enter email", required: true %>
    </li>
  </ul>
  <div class="card-footer">
    <%= form.submit "Save", class: "btn btn-secondary float-right" %>
  </div>
</div>
<% end %>
```

Now add in another section for the repositories to the form, pretty simple just a label and div with a loading message which attaches our [Stimulus controller](https://stimulus.hotwire.dev/reference/controllers).

```erb
<li class="list-group-item" data-reflex-root="#repositories">
  <label class="required">Repositories</label>
  <div id="repositories" data-controller="repositories">
    <i>Loading...</i>
  </div>
</li>
```

Next we add the Stimulus controller `/app/javascript/controllers/repositories_controller.js`.

```ruby
import ApplicationController from './application_controller';
import StimulusReflex from 'stimulus_reflex';
export default class extends ApplicationController {
  connect() {
    super.connect();
    StimulusReflex.register(this);
    this.loadRepositories();
  }
  loadRepositories() {
    if (this.isActionCableConnectionOpen()) {
      this.stimulate('Repositories#load_repositories');
    }
    else {
      setTimeout(function () {
        this.loadRepositories();
      }.bind(this), 100);
    }
  }
}
```

Two important things to note in this controller. One, we need to register this [Stimulus](https://stimulus.hotwire.dev/) controller with [StimulusReflex](https://docs.stimulusreflex.com/) so we can call `stimulate`. Two, we need to check if `isActionCableConnectionOpen`, if it’s not then wait and try again.

Next we add our [StimulusReflex](https://docs.stimulusreflex.com/rtfm/reflex-classes) which does the bulk of the work lazy loading the repositories.

```ruby
class RepositoriesReflex < ApplicationReflex
  delegate :current_user, to: :connection
  delegate :view_context, to: :controller
  def load_repositories
    user = User.new
    user.repositories.build
    form = ActionView::Helpers::FormBuilder.new(:user, user, view_context, {})
    github = Octokit::Client.new(access_token: current_user.token)
    github.auto_paginate = true
    repositories = github.repos({}, query: { type: "any", sort: "asc" })
    morph "#repositories", render(partial: "/users/repositories", locals: { form: form, repositories: repositories })
  end
end
```

Some important things in this reflex. One, since our partial requires a form object, we use [ActionView::Helpers::FormBuilder](https://docs.stimulusreflex.com/rtfm/working-with-forms#modifying-forms-with-morphs) to create a form for a user building an initial repository. Since it requires a [view_context](https://docs.stimulusreflex.com/rtfm/working-with-forms#modifying-forms-with-morphs), we leverage a delegate to access it from the controller. Two, the [Octokit](https://github.com/octokit/octokit.rb) client requires a user authentication token, so we add delegate to the connection so we can access the [current_user](https://docs.stimulusreflex.com/rtfm/authentication#devise). Three, rather than triggering a full [page morph](https://docs.stimulusreflex.com/rtfm/morph-modes#page-morphs), we instead can use a [selector morph](https://docs.stimulusreflex.com/rtfm/morph-modes#selector-morphs) so it only updates the div with id `#repositories`.

Finally we add the `/app/views/users/_repositories.html.erb` partial.

```erb
<% repositories.each do |repository| %>
  <%= form.fields_for :repositories, repository do |ff| %>
    <div class="form-check form-check-inline">
      <%= ff.check_box :checked, class: "form-check-input" %>
      <%= ff.label :checked, repository.full_name.downcase, class: "form-check-label mr-2 mb-1" %>
    </div>
    <%= ff.hidden_field :uid, value: repository.id %>
    <%= ff.hidden_field :name, value: repository.name %>
    <%= ff.hidden_field :path, value: repository.full_name %>
    <%= ff.hidden_field :url, value: repository.html_url %>
  <% end %>
<% end %>
```

_That’s it!_ So here’s what’s happening:

1. The initial form is loaded displaying the Loading… placeholder.
2. The Stimulus [connect](https://stimulus.hotwire.dev/reference/lifecycle-callbacks#methods) method is called when the controller first loads.
3. In Stimulus controller checks `isActionCableConnectionOpen` then calls [this.stimulate(‘Repositories#load_repositories’)](https://docs.stimulusreflex.com/rtfm/reflex-classes) otherwise waits and tries again.
4. The StimulusReflex uses delegates to access [view_context](https://docs.stimulusreflex.com/rtfm/working-with-forms#modifying-forms-with-morphs) and [current_user](https://docs.stimulusreflex.com/rtfm/authentication#devise).
5. The StimulusReflex builds a form object using [ActionView::Helpers::FormBuilder](https://docs.stimulusreflex.com/rtfm/working-with-forms#modifying-forms-with-morphs).
6. We make the external call to GitHub’s API to get user’s repositories.
7. We trigger a [selector morph](https://docs.stimulusreflex.com/rtfm/morph-modes#selector-morphs) to render our partial passing in the locals for form and repositories.
StimulusReflex then replaces the `#repositories` div with the partial displaying our list of repositories.

[Stimulus](https://stimulus.hotwire.dev/) is powerful library that lets you _“sprinkle your HTML with controller, target, and action attributes”_. And [StimulusReflex](https://docs.stimulusreflex.com/) lets you trigger server side code to do page updates. Together they are a powerful combination for [Rails](https://rubyonrails.org/)!