Turbo response always using layout from controller

Using turbo rails gem. After watching videos so far I was expecting returning HTML from turbo requests to be automatically with layout: false, however my actions are getting returned with full layout. Everything is working, however much slower than expected due to this .

Am I missing something?

1 Like

Interesting, turbo is supposed to return the view/partial content. even the content that dont belong to the specified turbo-frame ( turbo will cut and use only the specified turbo-frame id ), but yeah, the layout shouldn’t be present on turbo fetch responses.

Do you have more details about that ?

Are you able to show us a sample of your code? It could be helpful in troubleshooting the problem.

Below is the code I have in my view and controller. Everything works fine, but full layout comes back as you can see from screenshot. I think its because Hotwire does full page if you are doing a fetch?

View:

    <%= form_for @criteria,
                 as: :criteria,
                 method: :get,
                 url: locations_url,
                 data: { controller: "search", "turbo-frame": "locations" } do |f| %>
          <%= f.search_field :query,
                             class: "form-control",
                             placeholder: t("common.buttons.search"),
                             autofocus: true,
                             data: { action: "debounced:input->search#trigger" } %>
    <% end %>


<turbo-frame id="locations">
  <% for location in @locations %>
   ...
  <% end %>
</turbo-frame>

Controller:

def index
    @locations = Location.order("name ASC")
    @criteria = Criteria.new(permit_criteria_params)
    @locations = @locations.where("lower(name) LIKE lower(?)", "#{@criteria.query}%") if @criteria.query.present?
  end

Stimulus controller:

import { Controller } from "stimulus"

export default class extends Controller {
    trigger(event) {
        this.element.requestSubmit();
    }
}

Chrome Response:

Rails will send the full page, including layout, if you request that controller action. Turbo Frames expects this—it searches the full page for the frame that you wanted to replace and take out its contents. But for cases like these (search, autocomplete) you could create a separate action and use a nil layout!

@andrew in the Chrome Dev Tools request you’ve shown in your screenshot, can you check the request headers and look if a Turbo-Frame header is being sent?

This will help determine whether the issue is in the view code or if something in your controller is overriding the behaviour that prevents the the layout being used for a Turbo Frames request.

@stgm I think your reply is not quite right. The turbo-rails gem includes a module that adds an optimisation to set the layout to false for Turbo Frames requests so @andrew’s expectation is correct.

2 Likes

Oh yes, I missed that completely!

1 Like

Hi everyone, thanks for questions.

@mikej, yes Turbo-Frame is present

Here is the rails log, and as you can see it recognises the request as a TURBO_STREAM, but still goes off and renders the layouts.

Getting this and tried many things to try narrow down the issue. I have a fairly complex ApplicationController, and when inheriting from just ActionController::Base , I still get the same issue.

I am trying to see if its some conflicting gem. Will have to try disable some slowly and see if something could be causing it.

Hi @andrew

One idea is to open up the source for Action View and put some trusty puts debugging in the layout method of layouts.rb to see what calls are being made to that method that might be overriding the layout -> { false if turbo_frame_request? }.

Hi @mikej @stgm I think I found the issue within my application anyway, pretty sure it will be same for everyone else. It is caused whenever I have controllers that set the layout at the controller level. It will force turbo to always use the layout.

Steps to reproduce

class TestController < ActionController::Base
  def index
  end 
end

Everything will work fine and application.html.erb layout will NOT be used

class TestController < ActionController::Base
  layout "custom"

  def index
  end 
end

custom.html.erb layout will be always used for turbo response and seems to take precendence.

Expectation is that turbo should never use the layout in its response

Ah, of course! Coincidentally, I have been using this behaviour to my advantage in my application.

In this application we have a lot of settings/administration screens that are presented in an easy-to-dismiss modal dialog. All of these screens have their own controllers and they are loaded on demand. To clean up the views, I have actually put all of the boilerplate into a layout and assigned this layout to each of the relevant controllers. This is the layout:

<turbo-frame id="modal">
<div class="modal-content">
    <% if content_for?(:title) %>
    <div id="modal-browser-header">
        <%= yield(:title) %>
    </div>
    <% end %>
    <div id="modal-browser-body">
        <%= yield %>
    </div>
    <% if content_for?(:footer) %>
    <div id="modal-browser-footer">
        <%= yield(:footer) %>
    </div>
    <% end %>
</div>
</turbo-frame>

Because of this, I must disagree that layouts should be ignored for Turbo Frame requests! Plainly ignoring layouts would make it impossible to do some of these nice optimizations.

I do understand that some layouts may be too heavy to needlessly render, so I hope we can find some middle ground :wink:

1 Like

That’s is quite interesting, at glance, I’d say that it’s the wrong behavior, but I can’t say for sure. Maybe you could open an issue on GitHub to make sure this is the expected behavior

Raised ticket here and also added comment on workaround that I came up with.

1 Like