Hotwire Discussion

Force re-fetching a turbo frame

I’m trying to use Bootstrap modals. I have a Stimulus controller bring up the modal whenever it shows up in the DOM and that works fine. The problem is when the user tries to open the same modal twice in a row (user opens the modal, dismisses it, and tries to open it again). The second time, the turbo-frame is never fetched from the server, presumably because the link destination is the same as the “src” attribute already in the DOM. The user opening a modal, dismissing it, and opening a different one with a different URL works fine.

I think I must need to force turbo to always fetch the link, even if the source is the same as the existing one, but I don’t know how to.

How much of the original Bootstrap js is still on your page? BS likes to cache modals, and you have to bust that cache if you want it to go back to the server. I ran into this before Stimulus was invented, because I like to re-use the same modal for different endpoints, and I was getting the behavior that after I had loaded one url into the modal body, hitting any other modal link would just show that modal again, rather than re loading it with the new endpoint.

Walter

I don’t think it has anything to do with the modal specifically. Something like this also doesn’t update more than once. Clicking the first time changes the turbo frame as expected; clicking the second time doesn’t hit the server at all.

form.html.slim:

= link_to 'Update time', ticket_update_time_path(@ticket), :data => {:"turbo-frame" => 'current_time'}
= render 'current_time'

_current_time.html.slim:

= turbo_frame_tag 'current_time' do
  = Time.now

I’m curious, if it hit the server once on the same page… why would you want it to hit the server again. Why not just show the modal again and the content it returned in the first request?

In the case of the modal, it’s just that the content appearing in the DOM makes the modal open, as handled by a Stimulus controller.

Even without the modal though, there are cases like my (admittedly contrived) example above where fetching the same URL twice wouldn’t actually give the same content.

See workaround here:

Thanks – it seems that a bunch of people have had similar issues since Beta 5 or so. There was supposed to be a fix for some of them, but it doesn’t do anything for me, so I’ll use one of the workarounds presented in there for the time being.

The fix you referred to targeted a scenario where the turbo-frame src changed from URL A to URL B and back to URL A. It was not intended to allow URL A to URL A.

I’ve been doing something similar. My approach was to reset the src on the modals containing frame when the modal is hidden.

I also open the modal when the modal controller connects - i.e. when a modal is squirted in to the DOM.

import { Controller} from "stimulus"

export default class extends Controller {
  initialize() {
    this.resetModal = this.resetModal.bind(this)
    this.focusFirstInput = this.focusFirstInput.bind(this)
  }
    
  connect() {
    debug("Modal Controller Connected.")
    this.modal = new bootstrap.Modal(this.element, {})
    this.frame = this.element.closest("turbo-frame")
    this.element.addEventListener('hidden.bs.modal', this.resetModal)
    this.element.addEventListener('shown.bs.modal', this.focusFirstInput)
    this.modal.show()
  }
    
  disconnect() {
    this.modal.hide()
    debug("Modal Controller Disconnected.")
  } 
    
  focusFirstInput() {
    let inp = this.element.querySelector('input:not(input[type=button],input[type=submit],button,input[type="hidden"])')
    if (inp) {
      inp.focus()
      if ( inp.dataset.select) inp.select()
    }
  }
  
  resetModal() {
    if (this.frame) this.frame.src = "" 
    this.element.removeEventListener('hidden.bs.modal', this.resetModal )
    this.element.removeEventListener('shown.bs.modal', this.focusFirstInput )
    this.element.remove()
  }
          
          
  cancel() {
    this.modal.hide()
  }  
}