Stimulus request has format JS when it should be HTML

Hello,

I have a basic Stimulus controller that does debounce and request submit for a input field (search behavior) and when I type in the request goes as HTML, with an index action, it returns well and replace everything (it responds true to turbo_frame_request?) but when I use the exact same thing in another website the request goes as JS and the request responds true to request.xhr? (using a show action) and of course it is not captured by the turbo_frame and it does not replace the content.

My question is why does it work for one and not for the other? I don’t have any turbo_frame surrounding the input field, just a form as the working page.

Thanks

This is difficult to troubleshoot without seeing examples.

1 Like

@tleish here is the code that is sending the request as JS and in the working page I use the exact same thing:

<%= form_with url: '', data: { testid: 'some-search', turbo_action: 'advance', controller: 'search', turbo_frame: 'entities-lists' }, method: :get do |form| %>
  <div class="mt-8">
    <%= render Design::SearchInputComponent.new(search: search[:query], form: form) %>
  </div>
<% end%>

<%= turbo_frame_tag 'entities-lists' do %>
  <div>
    some content to be replaced when searching
  </div>
<% end %>

This is the Stimulus controller used in both cases:

import { Controller } from '@hotwired/stimulus';
import debounce from 'lodash.debounce';

export default class extends Controller {
  initialize() {
    this.submitWithDebounce = debounce(this.submit, 300).bind(this);
  }

  submit() {
    if (this.element.requestSubmit) {
      this.element.requestSubmit();
    } else {
      const submitEvent = new Event('submit', {
        bubbles: true,
        cancelable: true,
      });
      this.element.dispatchEvent(submitEvent);
    }
  }
}

By this text from the Rails documentation:

In Rails 6.0 and 5.2, all forms using `form_with` implement `remote: true` by default. These forms will 
submit data using an XHR (Ajax) request. To disable this include `local: true` . To dive deeper see 
[Working with JavaScript in Rails]
(https://guides.rubyonrails.org/working_with_javascript_in_rails.html#remote-elements) guide.

I’m using now:

<%= form_with url: '', local: true, data: { ...

and now it is refreshing the entire page, what I need now is to have the specific turbo frame replaced, I mean the request is not a turbo_frame request yet.

Can you share the sent request headers? Even a screenshot of the request from the developer panel of your browser will do.

@tleish I could receive the request as TURBO_STREAM by add this data: { turbo_stream: '', ... to the form and then modifying the Stimulus search_controller like this:

import { Controller } from '@hotwired/stimulus';
import debounce from 'lodash.debounce';

export default class extends Controller {
  static values = {
    params: Object,
    fieldName: String
  }

  initialize() {
    this.submitWithDebounce = debounce(this.submit, 300).bind(this);
  }

  async submit() {
    const turboStream = this.element.dataset.turboStream;

    if (turboStream != null) {
      const searchField = this.element.querySelector(`input[name="${this.fieldNameValue}"]`);
      const searchTerm = searchField.value != null ? searchField.value : '';

      if (this.paramsValue == null) this.paramsValue = {};

      await fetch(
        `${this.element.action}?${new URLSearchParams({
          ...this.paramsValue,
          search: searchTerm,
        })}`,
        {
          method: this.element.method,
          headers: {
            'Accept': 'text/vnd.turbo-stream.html',
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        }
      );
    } else {
      if (this.element.requestSubmit) {
        this.element.requestSubmit();
      } else {
        const submitEvent = new Event('submit', {
          bubbles: true,
          cancelable: true,
        });
        this.element.dispatchEvent(submitEvent);
      }
    }
  }
}

The problem now is that the view variant is returning nothing and I don’t know why. I’m using a variant like this: show.html+search.erb

What backend are you using? Please share the actual headers being sent.

@tleish The backend I’m using is now an index action from my SearchesController.

These are the headers being sent:

At quick glance, it’s not completely clear to me the logic of the stimulus controller or why you need to use a stimulus controller to submit a form to a turbo-frame, but when submitting a page through a turbo-frame, turbo adds the header Turbo-Frame: entities-lists. I see from the headers this is missing.

1 Like

@tleish I’m sending now the Turbo-Frame header but the index.html.erb view is not returning anything even though it has content.

Can you provide a github repo demo?

@tleish This is what I have right now

Stimulus controller:

import { Controller } from '@hotwired/stimulus';
import debounce from 'lodash.debounce';

export default class extends Controller {
  static values = {
    params: Object,
    fieldName: String,
    turboFrame: String
  }

  initialize() {
    this.submitWithDebounce = debounce(this.submit, 300).bind(this);
  }

  async submit() {
    const turboStream = this.element.dataset.turboStream;

    if (turboStream != null) {
      const searchField = this.element.querySelector(`input[name="${this.fieldNameValue}"]`);
      const searchTerm = searchField.value != null ? searchField.value : '';
      const turboFrame = this.turboFrameValue != null ? this.turboFrameValue : '';

      if (this.paramsValue == null) this.paramsValue = {};

      await fetch(
        `${this.element.action}?${new URLSearchParams({
          ...this.paramsValue,
          search: searchTerm,
        })}`,
        {
          method: this.element.method,
          headers: {
            'Accept': 'text/html, application/xhtml+xml',
            'Turbo-Frame': turboFrame,
          },
        }
      );
    } else {
      if (this.element.requestSubmit) {
        this.element.requestSubmit();
      } else {
        const submitEvent = new Event('submit', {
          bubbles: true,
          cancelable: true,
        });
        this.element.dispatchEvent(submitEvent);
      }
    }
  }
}

SearchItemsComponent:

<%= form_with(
        url: searches_path(account),
        data: {
          turbo_stream: '' ,
          testid: 'search-field-test',
          controller: 'search',
          turbo_frame: 'searches-frame',
          search_field_name_value: :searches_entities,
          search_turbo_frame_value: 'searches-frame'
        },
        method: :get) do |form| %>
    <div class="mt-8">
      <%= render Design::SearchInputComponent.new(search: search[:query], form: form, field_name: :searches_entities) %>
    </div>
<% end %>

<%= turbo_frame_tag 'searches-frame' do %>
<% end %>

SearchesController:

# frozen_string_literal: true

module App
    class SearchesController < ::App::BaseController
      def index
        @search = { query: search_params[:search] }
      end

      def search_params
        params.permit(:search)
      end
    end
end

SearchesController index view file:

<%= turbo_stream.update('searches-frame') do %>
  <%= "Rafa #{@search[:query]}" %>
<% end %>

The issue now is that the index view is not rendering anything back on the request.

Thanks

Your response does not include a turbo-frame tag.

I suggest you google some tutorials on how to use turbo-frames.

@tleish even though I add a turbo-frame tag I do not get anything, look:

<%= turbo_frame_tag 'searches-frame' do %>
  <%= "Rafa #{@search[:query]}" %>
<% end %>

Hey @titanve ,

I’d suggest to go back to basics and remove the entire JS, see what happens then. Remove local: true/false from the form as well.

I don’t think in this case you’d need Turbo Stream, or custom headers / custom request. Rremove turbo_stream: '' this.

This should be a turbo frame turbo_stream.update('searches-frame') do as @tleish suggested.

I’d also suggest removing the turbo frame and seeing if the request is HTML then. If it’s not, in Firefox, you can see what JS events are attached to the form element in the inspector, and spot any other JS that might be coming into play.

Let us know how it goes