inertia icon indicating copy to clipboard operation
inertia copied to clipboard

Scroll regions inside page component are not restored after form submit.

Open ragulka opened this issue 2 years ago • 15 comments

Versions:

  • @inertiajs/inertia version: 0.11.0
  • @inertiajs/inertia-vue3 version: 0.6.0

Describe the problem:

When a page, for example, an edit form contains a scroll-region and we use either the form helper or Inertia.post with { preserveScroll: true, preserveState: false }, and then server redirects back to the form after a successful save, the scroll-region scroll position is reset, instead of being preserved.

When the scroll-region is outside the page component (in the Layout or the blade template, for example), it works as expected - the scroll position is maintained. This is because neither the layout nor the blade template get re-rendered.

Note that the issue only happens when using preserveState with either false or 'error'. This is natural, because when the state is preserved, there are no DOM changes. However, when the state is refreshed from the server, the component is re-mounted, DOM changes and this resets the scroll position.

Inertia has a restoreScrollPositions() method, that seems like is built for restoring scroll positions inside scroll regions. However, this is never called after making a form submit.

What happens is that when the request is successful, setPage is called with the correct preserveScroll flag, but setPage itself only uses this flag to reset scroll position to the top of the page, if preserveScoll: false.

This relies on the fact that if the document body is scrollable, then browsers would maintain the scroll position. However, browsers will not maintain scroll positions of the individual scroll regions inside the page component.

It seems to me that restoreScrollPositions() should be called somewhere in the setPage method, after the swapComponent resolves.

Steps to reproduce:

  1. Create a scroll-region inside a page that has a form (such as an edit page)
  2. Ensure that when the form submits, it uses the { preserveState: 'errors', preserveScroll: true } options.
  3. Ensure the server redirects back to the same page
  4. Scroll the scroll-region inside the page
  5. Submit the form
  6. Notice how the scroll position inside the scroll-region is reset.

For easier reproduction, I've opened a PR on my fork with exact steps: https://github.com/ragulka/pingcrm/pull/1

Note: this issue may be related to https://github.com/inertiajs/inertia/issues/1181

ragulka avatar Jul 01 '22 11:07 ragulka

I think I have the same issue. Clicking a link inside a scroll-region, with preserveScroll, doesn't preserve the scroll position after the new page is loaded, even though the scroll-region is also on the new page (it's a sidebar).

jezzdk avatar Oct 20 '22 22:10 jezzdk

Hey! Thanks so much for your interest in Inertia.js and for sharing this issue/suggestion.

In an attempt to get on top of the issues and pull requests on this project I am going through all the older issues and PRs and closing them, as there's a decent chance that they have since been resolved or are simply not relevant any longer. My hope is that with a "clean slate" me and the other project maintainers will be able to better keep on top of issues and PRs moving forward.

Of course there's a chance that this issue is still relevant, and if that's the case feel free to simply submit a new issue. The only thing I ask is that you please include a super minimal reproduction of the issue as a Git repo. This makes it much easier for us to reproduce things on our end and ultimately fix it.

Really not trying to be dismissive here, I just need to find a way to get this project back into a state that I am able to maintain it. Hope that makes sense! ❤️

reinink avatar Jul 28 '23 01:07 reinink

I'm having this issue is well. scroll-region seems to be useless when used inside of a Vue component (for the reasons thoroughly described above). I'm surprised, because I would assume the test case would be using it inside of a Vue component. Also it's a shame this got closed out, because this is very much still an issue! @reinink

troygilbert avatar Aug 17 '23 15:08 troygilbert

@troygilbert As per reininks comment, you can simply open a new ticket and include the requirements as described.

jezzdk avatar Aug 17 '23 15:08 jezzdk

Hey happy to just reopen this one if it's still a known issue 👍

reinink avatar Aug 17 '23 15:08 reinink

I'm experiencing the same issue

Yiddishe-Kop avatar Oct 15 '23 14:10 Yiddishe-Kop

Same here, I have to add preserveState to get it work.

YakuBrangJa avatar Dec 17 '23 06:12 YakuBrangJa

Experiencing the same issue here. It just doesnt seem to work within Vue components.

medabkari avatar Jan 13 '24 23:01 medabkari

Exactly the same issue here, I also noticed that when settings for example a div with overflow, with scroll-region, and we console.log() the page element after onSuccess() method, like so :

form.post('some/route/', {
    onSuccess: page => { console.log(page) }
})

The array scrollRegions is empty in the browser's inspector (see screenshot below)

Capture d’écran du 2024-01-19 07-08-07

axel-paillaud avatar Jan 16 '24 06:01 axel-paillaud

This is still happening and I am thinking of a workaround.

baoanhng avatar Jan 23 '24 08:01 baoanhng

Hello there, I have started to investigate this "issue" (is it really an issue ?), and this is what I found :

In packages/core/src/router.ts, we have method setPage like so :

  protected setPage(
    page: Page,
    {
      visitId = this.createVisitId(),
      replace = false,
      preserveScroll = false,
      preserveState = false,
    }: {
      visitId?: VisitId
      replace?: boolean
      preserveScroll?: PreserveStateOption
      preserveState?: PreserveStateOption
    } = {},
  ): Promise<void> {
    return Promise.resolve(this.resolveComponent(page.component)).then((component) => {
      if (visitId === this.visitId) {
        // console.log(page.scrollRegions) is always undefined at this stage
        // with this.page, it work
        page.scrollRegions = this.page.scrollRegions || []
        page.rememberedState = page.rememberedState || {}
        replace = replace || hrefToUrl(page.url).href === window.location.href
        replace ? this.replaceState(page) : this.pushState(page)
        this.swapComponent({ component, page, preserveState }).then(() => {
          if (!preserveScroll) {
            this.resetScrollPositions()
          } else {
            // I added this line here
            this.restoreScrollPositions();
          }
          if (!replace) {
            fireNavigateEvent(page)
          }
        })
      }
    })
  }

As you can see, I have added a comment where I did some modification. Like this, it works, the scroll-region is well-preserved on an element that have overflow set to scroll (tested on Vue3 playground).

But I have some questions, obviously I can't make a PR, because I "cheat" with this.page instead of page. If I don't do that, page.scrollRegions is always undefined, even with scroll-region and a scroll event.

  • Why, in Promise, console.log(page.scrollREgions) is always undefined ? I think it's because it was defined asynchronously elsewhere, but where ?

If you guys have some idea, or advice, of course, feel free to tell me. I will continue to investigate this issue, but maybe this can already be useful.

The repos with playground set with this code is available on my GitHub account

axel-paillaud avatar Jan 26 '24 05:01 axel-paillaud

Hello there,

I have made a npm module with this modification, for testing purpose on a real project.

If you want to give it a try : npm install @inertiajs-fix-scroll/vue3 (for vue3 packages).

Then, change all occurrences of @inertiajs/vue3 to @inertiajs-fix-scroll/vue3 in your project.

For me, it works with Svelte, Vue3, Vue2. I can't test it with React, because I have a lot of bugs with the React playgrounds, and I don't really investigate why.

Again, it's just for testing purpose

axel-paillaud avatar Feb 05 '24 06:02 axel-paillaud

Has anyone been able to successfully work around this issue for a production site?

joecacti avatar Jun 17 '24 15:06 joecacti

Has anyone been able to successfully work around this issue for a production site?

I finally chose to don't use Inertia for local dynamic refresh ('ajax'), and fetch data with native javascript fetch() API and a regular Laravel route.

You can see demo here

repos here

And code example here (with axios but fetch() native javaScript API is better imo)

After a lot of research and try, it's the simplest solution I have found

axel-paillaud avatar Jun 17 '24 17:06 axel-paillaud

Still having this issue as well. Issue where i put the scroll-region on an overflow element but still the scrollbar reset.

gtim04 avatar Jun 27 '24 02:06 gtim04