Render Turbo Frame Upon Form Submit

I have a form that makes a request to a non-CRUD service class. It is a loan calculator that requires the amount, interest rate, and length. This calculator is tied to a Quote model, but since the service class is not creating or updating any records I am using a generic form.

I would like the form to submit and replace a frame on the same page so the returned data is represented on the page.

When I submit my form I get Error: Form responses must redirect to another location. I do not want to redirect to another location - all I want to do is render the returned turbo frame.

My question is - how do I submit inputs and return the frame on the same page? Is this a case where I should be using Turbo Streams instead? It seems like a case for Frames since I am only updating 1 part of the page. Why do I have to redirect after a form submission?

The route I am using is POST /quotes/loan_calculator in case that impacts anything.

quotes_controller.rb

  def loan_calculator
    @lease_option = LoanCalculator.new(quote_params).run
    render partial: "quotes/shared/loans/loan_options"
  end

new.html.erb

  <%= form_with url: loan_calculator_quotes_path do |form| %>
    <div>
      <%= form.label "quote[term_months]", "Months" %>
      <%= form.number_field "quote[term_months]", required: true, min: 0 %>
    </div>

    <div>
      <%= form.label "quote[interest_rate]", "Interest Rate" %>
      <%= form.number_field "quote[interest_rate]", required: true, min: 0 %>
    </div>

    <div>
      <%= form.label "quote[amount]", "Amount" %>
      <%= form.number_field "quote[amount]", required: true, min: 0 %>
    </div>

    <%= form.submit "Calculate" %>
  <% end %>

  <%= turbo_frame_tag :lease_option do %>
      <div>
        <p>Base Payment</p>
      </div>
    
      <div>
        <p>Total Lease Amount</p> <%= %>
      </div>
<% end %>

_loan_options.html.erb

<%= turbo_frame_tag :loan_option do %>
  <div>
    <p>Base Payment</p> <%= @loan_option.payment %>
  </div>

  <div>
    <p>Total Loan Amount</p> <%= @loan_option.total_payment %>
  </div>
<% end %>

Since you’re not using Turbo Streams. The controller returns an html partia, i.e ending with .html.erb. Turbo then expects the controller to redirect.

You need to specify the frame to replace on the form itself. See Turbo Reference (hotwired.dev)

  <%= form_with url: loan_calculator_quotes_path, data: { turbo_frame: : loan_option} do |form| %>

Also, it seems that your turbo frames don’t have matching id. On in the form has lease_option and the partial has loan_option. Frames need to have a matching id in order to replace themselves

3 Likes

That worked! Thank you for the answer.

As a general Hotwire rule - does it make more sense to use Streams for submitting a form and staying on the same page? I only used Frames since it was 1 update.

Glad it worked!.

I think that mostly is subjective, if you see on the Turbo issues and multiple blog posts all of them use their own “opinionated” idea when to use Streams of Frames.

For me, when i’m replacing parts of the page, i.e navigating away from it i would use frames. But, for cases when Frames don’t suffice. Like updating multiple parts of the page at once it is better to use Streams.

The solution you came up with seems valid in my opinion. Both could work in your case too.

For example, i’ve seen multiple resources using frames for displaying modals. For me, i have a global

<div id="modal"></div>

in layouts/application.html.erb that i replace whenever i need to display a modal on a specific action.

<%= turbo_stream.replace "modal"  do %>
<div id="modal">
  // content here
</div>
<% end %>

Interesting! Yea, I understand a lot of it is subjective opinion, but just trying to gather what others think so we hopefully move towards a convention.

Very clever solution for displaying modals - flexible and dry. I like it a lot! Thanks again for the answer earlier

1 Like