-1

I am trying to dynamically render form errors using a turbo-frame. This works great when there is an error and correctly to populates the form with the submitted params, but when the form submit is successful the redirect doesn't work.

Here is the code I tried:


app/views/orders/show.html.erb

...
<%= render "order_review_form", locals: {order: @order} %>
...

app/views/_order_review_form.html.erb

<%= turbo_frame_tag "order_review_form" do %>
  <% if local_assigns[:error] %>
    <p class="alert alert-danger alert-dismissible fade show">
      <%= sanitize(local_assigns[:error]) %>
      <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </p>
  <% end %>

  <%= form_with(model: Review.new, url: order_review_path(local_assigns[:order])) do |f| %>
    <div>
      <%= f.label :comment %>
      <%= f.text_field :comment, value: params[:comment] %>
    </div>

    <%= submit_tag "Submit review", class: "btn btn-sm btn-success" %>
  <% end %>
<% end %>

app/controllers/order_reviews_controller.rb

...
def create
  OrderReviewCreator.call(@order, params)
  redirect_to(order_path(@order), status: :see_other) # this doesn't work, the page doesn't update
rescue AppError => e
  render(
    "_order_review_form",
    locals: {
      order: @order,
      error: e.detail,
    },
    status: :unprocessable_entity,
  )
end
...

1 Answer 1

0

The solution was to use a turbo stream response in the controller instead of a turbo frame.


app/views/orders/show.html.erb (no changes)

...
<%= render "order_review_form", locals: {order: @order} %>
...

app/views/shared/_form_error.html.erb

Notes:

  • New partial to house only the form error
  • Note that the target local is what links all of this together across the view and the controller
<div id="<%= target %>">
  <% if local_assigns[:error] %>
    <p class="alert alert-danger alert-dismissible fade show">
      <%= sanitize(local_assigns[:error]) %>
      <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </p>
  <% end %>
</div>

app/views/_order_review_form.html.erb

Changes:

  • Removed the wrapping turbo frame tag. Note the target local being passed in.
  • Rendering the new form error partial
  • Removed the value: params[:comment] from the text field, it's not needed anymore since we're just replacing only the error section and not the entire form
<%= render(partial: "shared/form_error", locals: { target: OrderReviewsController::FORM_ERROR_TARGET }) %>

<%= form_with(model: Review.new, url: order_review_path(local_assigns[:order])) do |f| %>
  <div>
    <%= f.label :comment %>
    <%= f.text_field :comment%>
  </div>

  <%= submit_tag "Submit review", class: "btn btn-sm btn-success" %>
<% end %>

app/controllers/order_reviews_controller.rb

Changes: Added the constant FORM_ERROR_TARGET so it can be referenced in the view and the controller and changed the render to render a turbo stream for the form error. Note that the FORM_ERROR_TARGET is passed in the render in two places. This effectively replaces whatever html element has the id of FORM_ERROR_TARGET with the contents of the partial.

...
FORM_ERROR_TARGET = "order-review-form-error"
...
def create
  OrderReviewCreator.call(@order, params)
  redirect_to(order_path(@order), status: :see_other)
rescue AppError => e
  render(
    turbo_stream: turbo_stream.replace(
      FORM_ERROR_TARGET,
      partial: "shared/form_error",
      locals: {
        target: FORM_ERROR_TARGET,
        error: e.detail,
      },
    ),
    status: :unprocessable_entity,
  )
end
...

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.