DEV Community

loading...

Lazy Load Form Fields In Rails Using StimulusReflex

Dale Zak
Senior mobile developer that was building and designing mobile apps, before mobile apps were cool.
Originally published at dalezak.Medium Updated on ・4 min read

This pattern can help lazy load form fields in Rails using StimulusReflex which can be useful when calling an external API or loading slow relationships.

stimulus_reflex_800

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.

<%= 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 %>
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Two important things to note in this controller. One, we need to register this Stimulus controller with StimulusReflex 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 which does the bulk of the work lazy loading the repositories.

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
Enter fullscreen mode Exit fullscreen mode

Some important things in this reflex. One, since our partial requires a form object, we use ActionView::Helpers::FormBuilder to create a form for a user building an initial repository. Since it requires a view_context, we leverage a delegate to access it from the controller. Two, the Octokit client requires a user authentication token, so we add delegate to the connection so we can access the current_user. Three, rather than triggering a full page morph, we instead can use a selector morph so it only updates the div with id #repositories.

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

<% 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 %>
Enter fullscreen mode Exit fullscreen mode

That’s it! So here’s what’s happening:

  1. The initial form is loaded displaying the Loading… placeholder.
  2. The Stimulus connect method is called when the controller first loads.
  3. In Stimulus controller checks isActionCableConnectionOpen then calls this.stimulate(‘Repositories#load_repositories’) otherwise waits and tries again.
  4. The StimulusReflex uses delegates to access view_context and current_user.
  5. The StimulusReflex builds a form object using ActionView::Helpers::FormBuilder.
  6. We make the external call to GitHub’s API to get user’s repositories.
  7. We trigger a selector morph 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 is powerful library that lets you “sprinkle your HTML with controller, target, and action attributes”. And StimulusReflex lets you trigger server side code to do page updates. Together they are a powerful combination for Rails!

Discussion (0)

Forem Open with the Forem app