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:load won't be triggered when a FormSubmission request fails #85

Closed
nyrf opened this issue Jan 7, 2021 · 10 comments
Closed

turbo:load won't be triggered when a FormSubmission request fails #85

nyrf opened this issue Jan 7, 2021 · 10 comments

Comments

@nyrf
Copy link

nyrf commented Jan 7, 2021

When a FormSubmission request fails and return 400-500 code, turbo:load won't be triggered. eg:

application.js

document.addEventListener('turbo:load', function () {
  console.log('turbo load')
  document.getElementById('demo').replaceWith('demo replace')
})

new.html.erb

<div id="demo">demo</div>
<%= form_with(model: @post) do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>
  <%= f.submit %>
<% end %>

posts_controller.rb

def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end
class Post < ApplicationRecord
  validates :title, presence: true
end

After submit form, it should show demo replace, but it showed demo because of turbo:load hasn't be triggered.

error2

More info SimoTod/alpine-turbo-drive-adapter#29 (comment)

@sstephenson
Copy link
Contributor

This is by design. The turbo:load event fires in response to a successful visit, and form submissions only trigger visits when the server responds with a redirect.

@nyrf
Copy link
Author

nyrf commented Jan 7, 2021

So, the PR #39 is useless? We often use turbo:load event to run some codes, if this is by design, after submit form fail and return 422, the code inside turbo:load won't run. So, how to run the codes inside turbo:load? Thanks.

@sstephenson
Copy link
Contributor

You shouldn't listen for the turbo:load event unless you specifically want to know that a visit—a navigation that changes the URL—has finished.

The Hotwire way is to put the code you want to run in the connect() method of a Stimulus controller, and then add a matching data-controller attribute to an element so that the code runs whenever the element appears on the page.

@nyrf
Copy link
Author

nyrf commented Jan 8, 2021

The Hotwire way is to put the code you want to run in the connect() method of a Stimulus controller, and then add a matching data-controller attribute to an element so that the code runs whenever the element appears on the page.

I see, thanks.

@SimoTod
Copy link

SimoTod commented Jan 8, 2021

@sstephenson Silly question, the page does get replaced though (it re-renders the old page triggering a turbo:render event).
If the goal is to keep the page as it is, why does it replace the content? Thanks

@florentdestremau
Copy link

I find the decision not to fire the turbo:load event not in line with the docs:

Turbo Drive evaluates <script> elements in a page’s each time it renders the page. You can use inline body scripts to set up per-page JavaScript state or bootstrap client-side models. To install behavior, or to perform more complex operations when the page changes, avoid script elements and use the turbo:load event instead.

So that means that if my form has some JS in it (ex. booting a bootstrap-datepicker with jquery, launching a google autocomplete...), the JS is not re-initialized but the the DOM has changed.

I ended up using turbo:render instead to boot my custom JS (I'm migrating my current app to turbo, so a lot of custom JS hanging around). I don't see why the docs would point me to turbo:load then if turbo:render is the more universal event ? Am I missing something ?

@rossinkiwi
Copy link

rossinkiwi commented Mar 5, 2021

Does this mean that all of our existing legacy code that once was based on:
$(document).ready( function( e ) { ... code to init legacy js like parsley, datatables, datetimepicker etc. ...}
Then replaced with:
$(document).on('turbolinks:load', function( e ) { ... legacy js code ...}
Then replaced with:
$(document).on('turbo:load', function( e ) { ... legacy js code ...}
Actually needs to be replaced with:
$(document).on('turbo:render', function( e ) { ... legacy js code ...}
to handle the case of a form submission that has validation errors?

But the Stimulus Handbook indicates that this might get triggered twice:

turbo:render fires after Turbo renders the page. This event fires twice during an application visit to a cached location: once after rendering the cached version, and again after rendering the fresh version.

Which could be problematic because some of that legacy js gets torn down on turbo:before-cache which wont get called between the two turbo:render events.

It would be great if there were a bit more backward compatibility with turbolinks...

@florentdestremau
Copy link

I ended up booting 3 times: turbo:render, turbo:load and load.

Because React seems to be idempotent, and our codebase seems to be as well, we haven't noticed a major memory leak or slowness. Still, this is upsetting because it's not advertised as such.

What I understand is that only stimulus code is automatically re-booted if necessary, but external dependencies are to be either idempotent and booted multiple times, or booted once and hope for no re-render @sstephenson ?

@buncis
Copy link

buncis commented Nov 23, 2021

I also have a react component that I attached using turbo:load
my current solution is

adding document listerner for turbo:load and turbo:render

  document.addEventListener('turbo:load', () => {
    let domContainer = document.querySelector('#like_button_container');
    if (domContainer) {
      ReactDOM.render(<MyComponent />, domContainer);
    }
  })
  
  document.addEventListener('turbo:render', () => {
    let domContainer = document.querySelector('#like_button_container');
    if (domContainer) {
      ReactDOM.render(<MyComponent />, domContainer);
    }
  })

What I understand is that only stimulus code is automatically re-booted if necessary, but external dependencies are to be either idempotent and booted multiple times, or booted once and hope for no re-render

if that's so
how about we attached the react component using stimulus controller?
I hope this solutions bring me no problemo in the futuro

import React from 'react'
import ReactDOM from 'react-dom'

const MyComponent = () => (
 <h2>React ❤️ Stimulus</h2>
)

import { Controller } from "@hotwired/stimulus"

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

 connect() {
   if (this.hasReactTarget){
     ReactDOM.render(<MyComponent />, this.hasReactTarget);
   }
 }
}

@james-em
Copy link

james-em commented Jan 19, 2022

One year later, do we have a proper workaround for this? Is turbo really encouraging us to use an event that is fired twice?

Thanks!

Solution: #520 (comment)

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