2

I am having a problem with scrolling, when navigating using react-router.

When I am navigating to a different component it is keeping the scroll position from the last navigation. However I want it to restore and start from the top whenever the navigation is changed.

 class App extends Component{
  render() {
   return(
    <BrowserRouter onUpdate={() => window.scrollTo(0, 0)} history={createBrowserHistory()}>
      <div style={{display: 'flex'}}>
        <div className='navBar'>
          <ul style={{listStyleType: 'none'}}>
            <li><Link to='/'>Home</Link></li>
            <li><Link to='/towns'>Towns</Link></li>
            <li><Link to='/pubs'>Pubs</Link></li>
            <li><Link to='/venues'>Venues</Link></li>
            <li><Link to='/cinemas'>Cinemas</Link></li>

          </ul>
        </div>

        <div style={{flex:1, padding:'0px'}}>
          {routes.map((route) => (
            <Route
              key={route.path}
              path={route.path}
              exact={route.exact}
              component={route.main}
              />
            ))}
          </div>
        </div>
      </BrowserRouter>
     )
   }
 }

However it doesn't work. I have a feeling the issue is with my style of navigation rather then the window.scrollTo() snippet.

Dmitry Shvedov
  • 3,169
  • 4
  • 39
  • 51
Cmcco91
  • 99
  • 1
  • 1
  • 11

3 Answers3

9

To my knowledge, since react-router v4, BrowserRouter doesn't have an onUpdate function.

Several ways to accomplish it. This will be the easiest...

components/ScrollIntoView.js

import { PureComponent } from "react";
import { withRouter } from "react-router-dom";

class ScrollIntoView extends PureComponent {
  componentDidMount = () => window.scrollTo(0, 0);

  componentDidUpdate = prevProps => {
    if (this.props.location.pathname !== prevProps.location.pathname) window.scrollTo(0, 0);
  };

  render = () => this.props.children;
}

export default withRouter(ScrollIntoView);

routes/index.js

import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Links from "../components/Links";
import Home from "../components/Home";
import About from "../components/About";
import Contact from "../components/Contact";
import ScrollIntoView from "../components/ScrollIntoView";

export default () => (
  <BrowserRouter>
    <div>
      <ScrollIntoView>
        <Links />
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
        <Links />
      </ScrollIntoView>
    </div>
  </BrowserRouter>
);

Or you can find another approach here: Scroll to the top of the page after render in react.js

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • excellent @Matt Carlotta. worked perfect. only thing I may want to change is, somehow make it so if I click back on the previous navigation again, have it remember where I was. However this is not a big issue for now and your answer has kicked goals. Thank you. – Cmcco91 Mar 12 '19 at 00:43
  • This only work if the page is visited for the first time, how can it be done so it does it every time? Thank you in advance – Javier Guzmán Jan 13 '22 at 05:39
  • This answer is rather old and contained a mistake by using the `location` object instead of the `location.pathname` string. That said, I've updated the answer, HOWEVER, I highly recommend using react-router-dom's [useLocation](https://reactrouter.com/docs/en/v6/api#uselocation) hook with a `useEffect` if possible. – Matt Carlotta Jan 13 '22 at 16:07
4

My solution using React hooks

import { useEffect, useRef } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';

const ScrollIntoView = ({ children, location }) => {
  const prevLocation = useRef();

  useEffect(() => {
    if (prevLocation.current !== location.pathname) {
      window.scrollTo(0, 0);
      prevLocation.current = location.pathname;
    }
  }, [location]);

  return children;
};

ScrollIntoView.propTypes = {
  children: PropTypes.node,
  location: PropTypes.object
};

export default withRouter(ScrollIntoView);

Anders Ekman
  • 323
  • 5
  • 17
  • 1
    useLocation() instead of withRouter if you really want to keep everything hook ways – forJ Aug 14 '20 at 03:01
0

This component will scroll to the top of the page when the location changes. Based on feedback on other solutions it uses useLocation.

import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'

const ScrollIntoView = ({ children }) => {
  const location = useLocation()

  useEffect(() => {
    window.scrollTo(0, 0)
  }, [location])

  return children
}

export default ScrollIntoView