8

I'm trying to use turbo_frame_tag in my Rails application to manage some tasks. I created a scaffold for my tasks.

I wrapped the page I want to use inside of a turbo frame tag as below:

<%= turbo_frame_tag "modal" do %>

      <h1>New task</h1>
      <%= render "form", task: @task %>
      <br>
      <div>
        <%# <%= link_to "Back to tasks", tasks_path %> %>
        <%= link_to "Cancel", "#", data: {
          controller: "modals",
          action: "modals#close"
        }, class: "cancel-button" %>
      </div>

<% end %>

enter image description here

In my home page index.html.erb file I added data to my Add button with the same tag:

<%= link_to "Add", new_task_path, data: { turbo_frame: "modal" }, class: "btn btn-secondary", remote: true %>

The modal is working correctly. It is opened when I click the Add button on my home page. When I try to submit my action to create a new task, I see on my terminal 200 Response and the new task is added to my database.

But (also) I get the "Content Missing" text information on my home page. The page isn't reloaded. In the developer browser I get this error:

turbo.es2017-esm.js:3650 Uncaught (in promise) Error: The response (200) did not contain the expected \<turbo-frame id="modal"\> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.
at c.delegateConstructor.throwFrameMissingError (turbo.es2017-esm.js:3650:15)
at c.delegateConstructor.handleFrameMissingFromResponse (turbo.es2017-esm.js:3646:14)
at c.delegateConstructor.loadFrameResponse (turbo.es2017-esm.js:3567:18)
at async c.delegateConstructor.loadResponse (turbo.es2017-esm.js:3441:34)

enter image description here

I have just started learning Ruby on Rails and everything is new for me. I would be grateful for any help and information if anyone had such a problem and how to deal with it.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
folwarczyk
  • 81
  • 1
  • 2

3 Answers3

5

This behavior is introduced and documented in this PR. Note that the hotwire documentation has been updated with a recommended solution.

You can specify that a page should redirect instead of rendering the Content Missing error by adding a [turbo visit control tag] (https://turbo.hotwired.dev/reference/attributes#meta-tags).

To make it easier, you can utilize a turbo helper method.

<!-- show.html.erb -->

<meta name="turbo-visit-control" content="reload">

<!-- or -->

<%= turbo_page_requires_reload %>

Alternatively, you can explicitly add a data: {turbo_frame: "_top"} to specify that a link should break out of the turbo_frame.

Thomas Van Holder
  • 1,137
  • 9
  • 12
4

When you redirect after the form submission, there is no <turbo-frame> with the same id, so you get frame missing error. For some details and my first attempt at a reusable solution:

https://stackoverflow.com/a/75704489/207090

I don't want to post a duplicate answer, but I'll mention that there is a turbo:frame-missing event, that you can listen for and handle the error:

document.addEventListener("turbo:frame-missing", (event) => {
  const { detail: { response, visit } } = event;
  event.preventDefault();
  visit(response.url);
});

One caveat with doing a simple visit(response.url) is that it will be the second request to that url (first one is from the redirect).

The ideal place to handle this would be at the redirect response, because that's where you'd know if form submission was successful or not and send redirect as a regular Turbo.visit outside of the frame. Which you can do by sending some javascript in a turbo_stream:

render turbo_stream: turbo_stream.append(
  request.headers["Turbo-Frame"],
  helpers.javascript_tag(
    "Turbo.visit('#{model_url(@model)}')",
    data: {turbo_temporary: true} # one time only; don't cache it
  )
)

To make it a bit prettier, do the same thing but in a custom turbo stream action.


Make a custom turbo_stream.redirect action

Tag for "redirect" action would look like this:

<turbo-stream action="redirect" target="/url"></turbo-stream>

Add new action to Turbo to handle it on the front end:
https://turbo.hotwired.dev/handbook/streams#custom-actions

// app/javascript/application.js

import { Turbo } from "@hotwired/turbo-rails";

Turbo.StreamActions.redirect = function () {
  Turbo.visit(this.target);
};

// or using event listener

document.addEventListener("turbo:before-stream-render", (event) => {
  const fallbackToDefaultActions = event.detail.render;
  event.detail.render = function (streamElement) {
    if (streamElement.action == "redirect") {
      Turbo.visit(streamElement.target);
    } else {
      fallbackToDefaultActions(streamElement);
    }
  };
});

Use generic action method to render it in a controller or in a template:

turbo_stream.action(:redirect, model_url(@model))

You can also add redirect method to turbo_stream helper:

# config/initializers/stream_actions.rb

module CustomTurboStreamActions
  # turbo_stream.redirect(model_url(@model))
  def redirect url
    action(:redirect, url)
  end
end
Turbo::Streams::TagBuilder.include(CustomTurboStreamActions)

Example of how to get out of the frame:

# app/controllers/*_controller.rb

def update
  respond_to do |format|
    if @model.update(model_params)
      format.turbo_stream do
        render turbo_stream: turbo_stream.redirect(model_url(@model)) 
      end
      format.html { redirect_to model_url(@model), notice: "Updated." }
    else
      format.html { render :edit, status: :unprocessable_entity }
    end
  end
end
Alex
  • 16,409
  • 6
  • 40
  • 56
  • One thing I don't quite understand... Won't the turbo frame request itself be a `format.html` request, not a `format.turbo_stream` request? That's what I think I'm seeing... I want to break out of the turbo-frame with a redirect. Can I use this (elegant) approach to extending turbo with a custom action in that scenario? – gap Mar 25 '23 at 16:45
  • @gap when you submit the form it should be a `turbo_stream` request. if you just need to click out of the frame add `data-turbo-frame="_top"` to the link. – Alex Mar 25 '23 at 21:53
  • I only know I need to break out based on server-side decisions (error case/ redirect). The client doesn't know or presume that – gap Mar 27 '23 at 12:57
  • @gap actually you should be able to respond to html turbo_frame requests with turbo_stream. if it doesn't work just post another question with your setup. – Alex Mar 30 '23 at 05:48
  • 1
    This should be added to the turbo-rails gem. – Ben Trewern Apr 06 '23 at 14:39
2

I got the same error message after updating from turbo-rails 1.3.3 to 1.4.0. It looks like this is a breaking change in 1.4.0. It happens because the current version of turbo-rails assumes that all actions lead to updates inside the turbo-frame. Of course that's not always the case. Especially not if the turbo-frame's content is a search result list. If you want that links inside the turbo-frame update the whole page, then you need to add target="_top" to the turbo-frame tag. Here a complete example:

<turbo-frame id="supplier_contracts" data-turbo-action="advance" target="_top" >

That solved the problem for me.

Robert Reiz
  • 4,243
  • 2
  • 30
  • 43