Search code examples
javascriptreactjsgoogle-chromereact-router

How to implement scroll restoration for React Router SPA


I'm building a React single-page application, and I've noticed the scroll restoration does not appear to work as expected in Chrome (and maybe other browsers.)

On the react-router-dom github repo, they have a page that says that browsers are starting to natively handle scrolling for single page apps, and the behavior would be similar to that of a traditional non-SPA web page, if history.scrollRestoration is set to auto.

The behavior I need is indicated on that page:

  1. Scrolling up on navigation so you don't start a new screen scrolled to the bottom.
  2. Restoring scroll positions of the window and overflow elements on "back" and "forward" clicks (but not Link clicks!)

But I also need to disable the behavior for routes that contain tabs or a carousel.

Here's a link to Chrome's spec on the issue.

What I'm observing in Chrome Version 78.0.3904.97 (Official Build) (64-bit) for OS X, is what appears to be what I'd expect from the history.scrollRestoration manual setting. That is, when I'm scrolled half way down a page, and I click a link, the next page is scrolled halfway down the page, to the same point as the previous page.

I've checked the history.scrollRestoration at various moments and find it starts and stays auto, the default.

One point of significance here is this from @TrevorRobinson's answer "the browser's automatic attempts at scroll restoration... ...mostly don't work for single-page apps..." Ok... I found consistent support for history.scrollRestoration, but apparently browsers are crappy at actually doing the scroll restoration. Then that should be noted here, which would have saved me time today.

Then I look up ways to do this manually, and I'm not clear of the way forward. react-router-scroll says it's not compatible with React Router v4, but doesn't mention v5, which is current as of this question. So should I assume v5 is also not compatible?

So I look further for a way forward, and found this SO answer about how to handle scroll restoration with React Router v4... Should I assume that will work with React Router v5? Update: It does not appear to work for me in v5. I tried several configurations. I also could not get it completely working with React Router v4. It came alive at least, it scrolls to top on a new page, but restoration in the history does not occur.

I did also get react-router-scroll-memory working, but it does not have options for disabling scrolling on designated routes, like what would be needed for tabs or a carousel... I might just hack it to make it work.

I considered using oaf-react-router, but it says nothing in the documentation about disabling scroll restoration or scroll-to-top for certain routes. Edit: It does actually handle this, as outlined in my answer.

Is there something I've overlooked? What is the standard for dealing with this issue, because I can't be the only one who needs this feature. It seems like I'm doing something edgy and experimental, but I just need my site to scroll like a normal site.


Solution

  • One way to do this is with oaf-react-router. Use the shouldHandleAction options in the settings. The function gets passed previousLocation and nextLocation which you can use to determine if scroll should be reset/restored, and return false if not.

    const history = createBrowserHistory();
    
    const settings = {
       shouldHandleAction: (previousLocation, nextLocation) => {
           // Inspect/compare nextLocation and/or previousLocation.
           return false // false if you don't want scroll restoration.
       }
    };
    
    wrapHistory(history, settings);
    

    oaf-react-router works with React Router v4 and v5, and handles accessibility, where many SPA routers do not, so it may be the most complete solution at this time.

    Curiously, the documentation does not elaborate on this feature.

    If anyone else has a solution that works and would be considered more of a standard, please provide an answer with it here, and I'll consider changing the selected answer.