Getting content missing error for a turbo frame but only when I broadcast with broadcast_append_to

Hi,

I have a system that resembles GitHub comments conceptually. Instead of Issues and Comments I have Questions and Answers.

I’m using Turbo Frames so that when I list all of a Question’s Answers the Answer author can optionally edit or delete their answer. When they click edit it uses Turbo Frames to load the form to edit their Answer.

There’s both a save button as well as a cancel link. Clicking save will update the Answer. Clicking cancel sends a regular GET request to the question such as /questions/3.

All of this is working normally when I don’t broadcast_append_later_to in my model. Before going into more detail about the error, here’s the set up.

The Answer’s update action looks like this:

      if @answer.update(answer_params)
        respond_to do |format|
          format.turbo_stream
        end
      else
        render :edit, status: :unprocessable_entity
      end

The create.turbo_stream.erb looks like this:

<%= turbo_stream.append "#{dom_id(@question)}_answers", partial: "courses/answers/answer", locals: { course: @course, question: @question, answer: @answer } %>

The update.turbo_stream.erb looks like this:

<%= turbo_stream.replace @answer, partial: "courses/answers/answer", locals: { course: @course, lesson: @lesson, question: @question, answer: @answer } %>

My Question’s show template has this:

<ul id="<%= "#{dom_id(@question)}_answers" %>">
  <%= render partial: "courses/answers/answer", collection: @answers, locals: { course: @course, lesson: @lesson, question: @question } %>
</ul>

In my answer.rb model, there’s:

    broadcast_append_later_to [question, :answers],
      target: "question_#{question.id}_answers",
      partial: "courses/answers/answer",
      locals: { course:, lesson: question.lesson, question: }

If I comment out that line in the model, the following workflow is successful:

  1. Create a new answer
  2. The answer author sees the answer through an XHR from the Turbo Stream
  3. The answer author can click edit which shows the answer form
  4. The answer author can click cancel
  5. The answer is shown without the form

If I manually reload my browser after 2 step it also works the same. If I edit the answer instead of clicking cancel then it works too (with and without a reload before clicking edit).

If I broadcast the answer, then everyone connected sees the answer correctly but then the cancel link doesn’t work the same for the answer author.

The problem is with the above workflow. If I execute the same 5 steps then when I click the cancel link, I get a content missing error. There’s no error on the back-end. I only see this in my JS console Uncaught (in promise) Error: The response (200) did not contain the expected <turbo-frame id="edit_answer_175"> and will be ignored.. But the source code has this turbo frame in it.

If I reload the page after step 2 then it works normally. This led me to think that the issue is related to when a broadcast occurs on the page, even if the broadcasted element is the person who created the element. Although in the network inspector in my browser, it’s not showing a websocket update when I submit an answer. This is being submit through an XHR to the author.

Editing and deleting the answer works fine, it’s only the cancel link that’s not working as expected.

Does anyone know what I might be doing wrong or how to further debug and fix this? I tried using both broadcast_append_later_to and broadcast_append_to, they both have the same issue.

Thanks. I’m using Rails 7.0.6 and the latest stable releases of everything Turbo related.

That’s the issue. Are you sure you have an element with id edit_answer_175 in the dom? You could refactor and do dom_id(@question, :_answers) etc and inside a class where you need to use dom_id just include it:

include ActionView::RecordIdentifier

Yep, both for the broadcasted element and non-broadcasted element when I do a full page reload. In both cases if I view the page source I see:

<turbo-frame id="edit_answer_175">
    <p>Hello world.</p>
</turbo-frame> 

Here’s a screenshot if I inspect the element after adding a new answer, then clicking edit and clicking the cancel link:

image

This produces an error of turbo.es2017-esm.js:3650 Uncaught (in promise) Error: The response (200) did not contain the expected <turbo-frame id="edit_answer_202"> and will be ignored..

The edit link has data: { turbo_frame: dom_id(answer, :edit) } and there’s the corresponding frame in the _answer.html.erb file:

<%= turbo_frame_tag dom_id(answer, :edit) do %>
    <%== answer.html %>
<% end %>

Note: I’m using <%== here because it’s trusted. This field is saved in the DB after converting Markdown to HTML with a limited set of tags.

I also have this in my answers/edit.html.erb:


<%= turbo_frame_tag dom_id(@answer, :edit) do %>
  <%= render "form", course: @course, lesson: @lesson, question: @question, answer: @answer %>
<% end %>

I believe turbo_stream needs a matching DOM element such as a div, section, span etc.

I believe turbo_stream needs a matching DOM element such as a div, section, span etc.

The interesting part in this case is the piece that malfunctions is a cancel link which is a GET request
through a regular link without Turbo Streams. I think frames are being used here? It’s just only an issue after the element appeared in the DOM through a Turbo Stream append.