hotwired/stimulus-rails

Mutation Observer not recognising DOM change when using Turbo Streams

crobbo opened this issue · 2 comments

From a Rails controller I use Turbo Streams to replace a Form element. The form is connected to a StimulusJS controller (input controller) and it was my understanding that the controller should disconnect and reconnect as the DOM element is replaced.

However, it doesn't appear to detect any DOM change and consequently does not connect the controller again after the Form element is replaced. My understanding is StimulusJS uses the DOM MutationObserver API to detect the DOM changes.

Why is it not picking up the DOM change when I use Turbo Streams?

Form element:

<%= form_with model: @game, method: :patch, html: { autocomplete: "off" },  data: { controller: "input" }, id: dom_id(@game), class: "grid justify-center justify-items-center grid-cols-5 gap-y-2 gap-x-2 max-w-md mx-auto mt-2" do |form| %>
    <%= form.fields_for :guesses, @game.guesses.order(:created_at) do |ff| %>
        <% if ff.object.value != '' %>
            <div id="input_<%= dom_id(ff.object)%>" class="w-16 h-16 ">
                <%= tag.div :value, class: ["flex justify-center items-center text-center uppercase text-3xl text-white rounded focus:border-cyan-400 w-full h-full", "bg-green-400": ff.object.result == 'match', "bg-orange-400": ff.object.result == 'occurs', "bg-gray-400": ff.object.result == 'miss' ] do %>
                    <%= ff.object.value %>
                <% end %>
            </div>
        <% else %>
            <div id="input_<%= dom_id(ff.object)%>" class="w-16 h-16 ">
                <%= ff.text_field :value, class: ["text-center uppercase caret-transparent text-3xl text-white bg-slate-800 border rounded focus:border-cyan-400 w-full h-full", "bg-green-400": ff.object.result == 'match', "bg-orange-400": ff.object.result == 'occurs', "bg-gray-400": ff.object.result == 'miss' ], data: {action: '', input_target: 'inputBox'}, maxlength: "1", autofocus: "true" %>
            </div>
        <% end %>
    <% end %> 
    <%= form.submit "Submit", class: "hidden" %>
<% end %>

Stimulus Controller:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["inputBox"]

  connect() {
    this.index = 0
    this.focusInput()
  }

  
  focusInput() {
    console.log("focusInput called")
  }
}

@crobbo It's not clear how you might be using this example, but there seems to be a misunderstanding on what stimulus does and doesn't do. Stimulus does use the MutationObserver API in a very simple way. It sets up to listen for element attributes, childlists Element Nodes and subtrees of the childlists for the "this" controller, so it does a deep search. However Stimulus only considers the attributes that are in the API as what is monitored for changes, that is actions, targets, classes, values and outlets.

As for Turbo turbo-frames or turbo-streams, there is no MutationObserver, But I guess that indirectly, but setting up a data-controller inline on the "turbo-xxxx" element, you can use the "this" for the turbo-stream for turbo-frame, perhaps pausing rendering by hooking into the events, and do something specific, then resume the rendering.

Turbo Streams are simple and elegant and just use the browser's API (since it is merely a subclass of an HTMLElement), so it uses the native browser callbacks and functions to implement the changes to the DOM. Turbo Frames are a little more advanced, because Turbo intercepts links and has to match the frames so a lot more is going on. Turbo Streams via Channels is more sophisticated but in essence uses WebSockets for channels along with a Redis connection, but if Turbo Streams is not via a channel, I believe that it simply uses a regular HTTP connection.

The Hotwire Turbo forum is a good place for questions like these. https://discuss.hotwired.dev/

Ok, thanks. Please close the issue as it's been a while since I was working on this and had this issue, as you say it was probably more to do with my lack of understanding at the time.