Skip to content

view.forceReloaded permanently set to true after frame navigation with data-turbo-action="advance", preventing scroll reset on subsequent Visits #1526

@Xeross99

Description

@Xeross99

Environment

  • @hotwired/turbo: 8.0.23 (via turbo-rails 2.0.23)
  • Browser: Chrome, Safari (all tested)

Description

When a <turbo-frame> with data-turbo-action="advance" navigates and the server responds with a frame-only layout (not a full HTML page), view.forceReloaded is permanently set to true and never reset. This causes performScroll() to skip scrollToTop() on all subsequent Visits for the lifetime of the page.

Steps to reproduce

  1. Create a <turbo-frame id="main" data-turbo-action="advance"> in your layout
  2. Use a frame-only layout for Turbo-Frame: main requests (standard optimization to reduce response size)
  3. Click a link with data-turbo-frame="main" to navigate the frame (URL advances)
  4. Now click any link with data-turbo-frame="_top" that should trigger a full Visit
  5. Expected: page scrolls to top
  6. Actual: scroll position is preserved (stuck at previous position)

Root cause

In renderPage():

renderPage(e, t = false, s = true, r) {
  const i = new RendererClass(this.snapshot, e, t, s);
  return i.shouldRender ? r?.changeHistory() : this.forceReloaded = true, this.render(i);
}

When the frame response is not a full HTML page, shouldRender returns false, so forceReloaded is set to true.

Then in performScroll():

performScroll() {
  this.scrolled || this.view.forceReloaded || this.view.shouldPreserveScrollPosition(this) || (
    "restore" == this.action
      ? this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop()
      : this.scrollToAnchor() || this.view.scrollToTop(),
    this.scrolled = true
  )
}

forceReloaded is true, so the entire scroll logic is skipped. Since forceReloaded is never reset to false, every subsequent Visit skips scroll reset.

Expected behavior

forceReloaded should be reset to false before each new Visit, e.g. at the start of Visit.start() or when a new Visit is constructed.

Current workaround

document.addEventListener("turbo:before-visit", () => {
  Turbo.navigator.view.forceReloaded = false
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions