ruby-on-railsstimulusjsstimulus-rails

Is there a way to Use Stimulus to toggle Boolean values in ruby on rails?


I am creating an example project for my company and have to set up flagging for posts. I have used forms to setup post flagging method for users to flag posts in case of bad content. Here is the view

<div class="form-check form-switch">
<%= form_with url: toggle_flag_post_path(@post), method: :patch, data: { turbo: false } do |f| %>
<%= f.check_box :flag, {
  class: "form-check-input toggle-flag-checkbox",
  role: "switch",
  data: { post_id: @post.id },
  onchange: "this.form.submit()"
}, 1, 0 %>
<%= f.label :flag, (@post.flag ? "Unflag" : "Flag"), class: "form-check-label" %>
<% end %>

and here is the requisite controller method

def toggle_flag
 @post.toggle!(:flag)
 redirect_back(fallback_location: @post) 
end

Is there a way to use Stimulus to toggle the flag without having to do a fullpage reload due to form.


Solution

  • You don't need Stimulus to do that. Rails has a convenient way to mutate parts of the page. It is called Turbo Streams. There are plenty tutorials on Turbo Streams in the web, here's a good one to start with.

    So the steps are:

    1. Extract your form into a template, e.g. "_flag_form.html.erb", and pass @post to the form:
    <%= render "flag_form", post: @post %>
    
    1. Wrap the form with a <div> element with a unique ID:
    <div id="<%= dom_id post %>-form">
      <!-- your form goes here -->
    </div>
    
    1. Enable turbo in your form: data: { turbo: true, turbo_stream: true }

    2. Replace onchange: "this.form.submit()" with onchange: "this.form.requestSubmit()" (this.form.submit() won't work with Turbo)

    3. Remove the redirect_back from the toggle_flag action:

    def toggle_flag
     @post.toggle!(:flag)
    end
    

    Instead of redirecting back we'll mutate only a certain part of the page! This is there the magic of Turbo Streams comes into play.

    1. Create a view for the action: toggle_flag.turbo_stream.erb. Rails will automatically fallback to this view after executing the toggle_flag action. In this template you can replace the form with the updated content:
    <%= turbo_stream.update "#{dom_id @post}-form" do %>
      <%= render "flag_form", post: @post %>
    <% end %>
    

    This code will find a <div> element with the provided ID (#{dom_id @post}-form), and it will update content of the <div> (i.e. it will re-render the form, since we have the render statement inside the block), while the rest of the page will remain the same!