Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop Reach Router scrolling down the page after navigating to new page

When I navigate to a new page, Reach Router scrolls down to the page content past the header (if the content is long enough). I'm assuming this is for accessibility but it's not necessary for my app and it's actually quite jarring. Can this behaviour be disabled?

Note that I'm talking about Reach Router not React Router.

Reach Router

like image 214
Evanss Avatar asked Oct 30 '18 05:10

Evanss


People also ask

How do I revert back to previous page on router?

To go back to the previous page, pass -1 as a parameter to the navigate() function, e.g. navigate(-1) . Calling navigate with -1 is the same as hitting the back button. Similarly, you can call the navigate function with -2 to go 2 pages back.

How do I scroll to top on Route Change react?

import { useLocation } from "react-router-dom"; const location = useLocation(); useEffect(() => { window. scrollTo(0,0); }, [location]); Setting location in useEffect will make sure to scroll to top on every path change. The simplest one that worked for me.


2 Answers

Try using <Router primary={false}> which will not focus on the route component.

https://reach.tech/router/api/Router

primary: bool

Defaults to true. Primary Routers will manage focus on location transitions. If false, focus will not be managed. This is useful for Routers rendered as asides, headers, breadcrumbs etc. but not the main content.

WARNING: If you are concerned about breaking accessibility please see this answer: https://stackoverflow.com/a/56996986/428780

like image 158
Vinicius Avatar answered Sep 24 '22 05:09

Vinicius


The top answer here, while solving the OP's problem, is probably not the solution most people want, since it turns off the most important accessibility feature of Reach router.

The fact Reach router focuses the content of the matched <Route> on a route change is for accessibility reasons - so screen readers etc can be directed to the newly updated, relevant content, when you navigate to a new page.

It uses HTMLElement.focus() to do this - see the MDN docs here.

The problem is that by default, this function scrolls to the element being focused. There is a preventScroll argument which can be used to turn this behaviour off, but the browser support for it is not good, and regardless, Reach Router does not use it.

Setting primary={false} turns this behaviour off for any nested <Router> you may have - it is not intended to set false on your main (primary) <Router> -- hence the name.

So, setting primary={false} on your primary <Router>, as the top answer suggests, 'works' in the sense that it stops the scrolling behaviour, but it achieves this by simply turning off the focusing behaviour completely, which breaks the accessibility feature. As I said, if you do this, you're breaking one of the main reasons to use Reach Router in the first place.

So, what's the solution?

Basically, it seems that this side effect of HTMLElement.focus() - scrolling to the focused element - is unavoidable. So if you want the accessibility feature, you have to take the scrolling behaviour with it.

But with that said, there might be a workaround. If you manually scroll to the top of the page using window.scrollTo(0, 0) on every route change, I believe that will not 'break' the focusing feature from an accessibility perspective, but will 'fix' the scrolling behaviour from a UX perspective.

Of course, it's a bit of a hacky and imperative workaround, but I think it's the best (maybe only) solution to this issue without breaking accessibility.

Here's how I implemented it

class OnRouteChangeWorker extends React.Component {
  componentDidUpdate(prevProps) {
    if (this.props.location.pathname !== prevProps.location.pathname) {
      this.props.action()
    }
  }

  render() {
    return null
  }
}

const OnRouteChange = ({ action }) => (
  {/* 
      Location is an import from @reach/router, 
      provides current location from context 
  */}
  <Location>
    {({ location }) => <OnRouteChangeWorker location={location} action={action} />}
  </Location>
)

const Routes = () => (
  <>
    <Router>
      <LayoutWithHeaderBar path="/">
        <Home path="/" />
        <Foo path="/foo" />
        <Bar path="/bar" />
      </LayoutWithHeaderBar>
    </Router>

    {/* 
        must come *after* <Router> else Reach router will call focus() 
        on the matched route after action is called, undoing the behaviour!
    */}
    <OnRouteChange action={() => { window.scrollTo(0, 0) } />
  </>
)
like image 40
davnicwil Avatar answered Sep 22 '22 05:09

davnicwil