Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbo Stream refresh does not work when responding to a form submission #1173

Open
jordinl opened this issue Feb 8, 2024 · 9 comments
Open

Comments

@jordinl
Copy link

jordinl commented Feb 8, 2024

I'm using Turbo 8 so I can trigger refreshes.

In one instance I submit a form (POST request) and I want to show a toast and refresh the page.

The page already has a toast container like this

<div data-turbo-permanent="" id="toast-container" class="position-fixed top-0 d-flex flex-column align-items-center">
</div>

When I submit the form, I see the server returns:

<turbo-stream action="update" target="toast-container"><template>
    <div class="toast align-items-center border-0" role="alert" aria-live="assertive" aria-atomic="true"
       data-controller="toast">
    <div class="d-flex alert alert-notice">
      <div class="toast-body">
        Marking 15 payouts ($198.45) as paid.
      </div>
      <button type="button" class="btn-xs btn-close me-1 m-auto alert-link btn-sm" data-bs-dismiss="toast" aria-label="Close"></button>
    </div>
  </div>

</template></turbo-stream>

<turbo-stream request-id="374d19b3-8358-462a-ae9d-0da43c02820b" action="refresh"></turbo-stream>

The toast is correctly shown, but the page is not refreshed.

@seanpdoyle
Copy link
Contributor

seanpdoyle commented Feb 28, 2024

@jordinl thank you for opening this issue.

Are you rendering the Morphing <meta> element into your page's header?

If the intention is to refresh the page with Morphed content (including the Flash), have you tried replacing the Turbo Stream template rendering with (assuming that you're using Rails) setting a flash.notice value and redirecting? Something like:

if @widget.save
  flash.notice = "Marking 15 payouts ($198.45) as paid."
  redirect_to widget_url(@widget)
end

@wawer77
Copy link

wawer77 commented Mar 8, 2024

Hey, found this issue when looking for answers to exactly the same problem (also Turbo 8, POST request).
When submitting the form and then responding with the following in turbo_stream template:

turbo_stream.update('venues', partial: 'venues')
turbo_stream.append('turbo-flash', flash_messages) 

everything works fine, the element is updated correctly.

However, when using these as a response to the same action:

turbo_stream_refresh_tag # tried also turbo_stream.action(:refresh, '_top')
turbo_stream.append('turbo-flash', flash_messages) 

Flash is shown, but page is not refreshed even though the response contains:

<turbo-stream action="refresh"></turbo-stream>
<turbo-stream action="append" target="turbo-flash"><template>...</template></turbo-stream>

The console says:

Error: <turbo-stream action="refresh">: unknown action

May that may be an indicator of a faulty local setup?

  • I tried adding the meta tag, but it doesn't help (I would prefer the default replace anyway).
  • I tried using redirect_to as @seanpdoyle suggested, but even though I am getting a correct response, the page is not refreshed (also with meta tag specifying replace) - maybe that's a lead? redirect_to would not be a preferable solution in my case, but I would go with that if it had worked.

I know it might be somehow due to a faulty setup on my side, but would like to establish a solution.

@seanpdoyle
Copy link
Contributor

@wawer77 could you change the following bug report template to reproduce your issue?

Save this file to `bug_report_template.rb`, then execute with `ruby bug_report_template.rb`
require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails"
  gem "propshaft"
  gem "sqlite3"
  gem "turbo-rails"

  gem "capybara"
  gem "cuprite", "~> 0.9", require: "capybara/cuprite"
end

require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
require "action_view/railtie"
# require "action_mailer/railtie"
# require "active_job/railtie"
# require "action_cable/engine"
# require "action_mailbox/engine"
# require "action_text/engine"
require "rails/test_unit/railtie"

class App < Rails::Application
  config.load_defaults Rails::VERSION::STRING.to_f

  config.root = __dir__
  config.hosts << "example.org"
  config.eager_load = false
  config.session_store :cookie_store, key: "cookie_store_key"
  config.secret_key_base = "secret_key_base"
  config.consider_all_requests_local = true
  config.turbo.draw_routes = false

  Rails.logger = config.logger = Logger.new($stdout)

  routes.append do
    root to: "application#index"
  end
end

class ApplicationController < ActionController::Base
  include Rails.application.routes.url_helpers

  class_attribute :template, default: DATA.read

  def index
    render inline: template, formats: :html
  end
end

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :cuprite, using: :chrome, screen_size: [1400, 1400], options: { js_errors: true }
end

Capybara.configure do |config|
  config.server = :webrick
  config.default_normalize_ws = true
end

ENV["DATABASE_URL"] = "sqlite3::memory:"
ENV["RAILS_ENV"] ||= "test"

Rails.application.initialize!

require "rails/test_help"

class TurboSystemTest < ApplicationSystemTestCase
  test "reproduces bug" do
    visit root_path

    assert_text "Loaded with Turbo"
  end
end

__END__

<!DOCTYPE html>
<html>
  <head>
    <%= csrf_meta_tags %>

    <script type="importmap">
      {
        "imports": {
          "@hotwired/turbo-rails": "<%= asset_path("turbo.js") %>"
        }
      }
    </script>

    <script type="module">
      import "@hotwired/turbo-rails"

      addEventListener("turbo:load", () => document.body.innerHTML = "Loaded with Turbo")
    </script>
  </head>

  <body>Loaded without Turbo</body>
</html>

@daniel-rikowski
Copy link

I'm seeing the same behaviour als @wawer77 and @jordinl

It looks like this is the intended behaviour. The refresh action is generated from the same request which caused the change in the first place. Turbo ignores that action on purpose.

See #1019 (comment)

@jordinl
Copy link
Author

jordinl commented Mar 8, 2024

@jordinl thank you for opening this issue.

Are you rendering the Morphing <meta> element into your page's header?

@seanpdoyle yes. I have:

    <meta name="turbo-refresh-method" content="morph">
    <meta name="turbo-refresh-scroll" content="preserve">

If the intention is to refresh the page with Morphed content (including the Flash), have you tried replacing the Turbo Stream template rendering with (assuming that you're using Rails) setting a flash.notice value and redirecting? Something like:

if @widget.save
  flash.notice = "Marking 15 payouts ($198.45) as paid."
  redirect_to widget_url(@widget)
end

This is a relatively old app. Up until recently the flash messages were shown as banner right below the navbar. Then I started adding more interactivity with Turbo Streams and it made sense to use a toast (positioned fixed). So we have some endpoints using a banner and others a toast. Which means I can't set the flash and redirect in this situation.

@jordinl
Copy link
Author

jordinl commented Mar 8, 2024

I'm seeing the same behaviour als @wawer77 and @jordinl

It looks like this is the intended behaviour. The refresh action is generated from the same request which caused the change in the first place. Turbo ignores that action on purpose.

See #1019 (comment)

@daniel-rikowski I'll have to read carefully the entire PR, I'm not entirely sure this is the same issue.

So far the way I got it working was to stream the toast update with action cable and then do the redirect:

    Turbo::StreamsChannel.broadcast_render_to([current_user, :payouts],
                                              target: 'toast-container',
                                              partial: 'shared/update_toast',
                                              locals: { flash: flash_details })

    redirect_to merchant_payouts_path(state: @state)

This seems worse than allowing to stream an update and a refresh in the same request.

@caleb
Copy link

caleb commented Mar 12, 2024

I was able to get a form submit request to refresh the current page by changing the "request-id" attribute of the turbo-stream tag:

In rails I did this:

render turbo_stream: turbo_stream.refresh(request_id: nil)

This caused the request id to be different from the submitting request ID and tricked turbo into rendering the refresh anyway.

In my case, I had a modal dialog that updated a record and I wanted the index page behind the modal to be refreshed with new content once submitted. The modal dialog uses a turbo frame to show the edit page.

EDIT:

The above code is using the a refresh method on the turbo streams tag builder from turbo-rails like from this PR: hotwired/turbo-rails#595

I'm guessing the user will always want to clear the request-id since otherwise the page will never refresh.

@gerardkabre
Copy link

gerardkabre commented Mar 27, 2024

@caleb Indeed that seems to be the case:

image

But this makes using frames to have an SPA like UI, creating in the same page the list is rendered for example, to not refresh and be forced to use stream actions to attach the new action

Show image image

Someone reading this knows if the idea going forward is to keep using streams for things like this and the auto refresh it's only meant to reflect updates from other people/browsers, or is there a way I'm missing? 🤔

@tonywok
Copy link

tonywok commented Mar 29, 2024

@caleb I was curious to see what happened when you send no request id and I was able to reproduce as you say -- what's interesting though is that my turbo frames without morph also seemed to re-load via their src.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

7 participants