React-router: Scroll position in RC1

Created on 30 Sep 2015  路  6Comments  路  Source: ReactTraining/react-router

First, thank you for creating and maintaining this incredibly useful library.

I'm having trouble getting scroll position to reset to the top of a page on navigation in RC1. My code looks something like the following:

var indexComponent = React.createClass({
    render() {
        return (
            <div>
                {this.children}
            </div>
        )
    }
});

var componentOne = React.createClass({
    mixins: [History],
    handleClick() {
        this.history.pushState(null, '/route-two')
    },
    render() {
        return (
            <div>
                <a onClick={this.handleClick}>
                    Link to Component 2
                </a>
            </div>
        )
    }
});

var componentTwo = React.createClass({
    mixins: [History],
    handleClick() {
        this.history.pushState(null, '/route-one')
    },
    render() {
        return (
            <div>
                <a onClick={this.handleClick}>
                    Link to Component 1
                </a>
            </div>
        )
    }
});

var routeConfiguration = {
    path: "/",
    indexRoute: {
        component: indexComponent
    },
    childRoutes: [
        {
            path: "route-one",
            component: componentOne,
            onEnter: doSomeAuthStuff,
            ignoreScrollBehavior: true
        },
        {
            path: "route-two",
            component: componentTwo,
            onEnter: doSomeAuthStuff,
            ignoreScrollBehavior: true
        }
    ]
};

React.render(
    <Router history={browserHistory} routes={routeConfiguration} />,
    document.getElementById('app')
);

Am I using the wrong part of the API to reset scroll position to the top of the page? If so, what's the proper way to trigger that behavior?

Most helpful comment

Using onUpdate didn't quite work out for me because I wanted to "preserve scroll position when using the back button, scroll up when you come to a new page".

Maybe onUpdate should have location passed to its arguments?

The workaround I found was this:

var history = createBrowserHistory();

history.listen(location => {
  // Use setTimeout to make sure this runs after React Router's own listener
  setTimeout(() => {
    // Keep default behavior of restoring scroll position when user:
    // - clicked back button
    // - clicked on a link that programmatically calls `history.goBack()`
    // - manually changed the URL in the address bar (here we might want
    // to scroll to top, but we can't differentiate it from the others)
    if (location.action === 'POP') {
      return;
    }
    // In all other cases, scroll to top
    window.scrollTo(0, 0);
  });
});

var routes = (
  <Router history={history}>
    // ...
  </Router>
);

All 6 comments

See #1958 However, setting the scroll position onUpdate as suggested didn't work for me for no comprehensible reason. I had to put it in the onEnter hook (which isn't nice but works for my case) :/

as @kaikuchn mentioned, use onUpdate:

<Router onUpdate={() => window.scrollTo(0, 0)} history={createBrowserHistory()}>
  ...
</Router>

Using onUpdate didn't quite work out for me because I wanted to "preserve scroll position when using the back button, scroll up when you come to a new page".

Maybe onUpdate should have location passed to its arguments?

The workaround I found was this:

var history = createBrowserHistory();

history.listen(location => {
  // Use setTimeout to make sure this runs after React Router's own listener
  setTimeout(() => {
    // Keep default behavior of restoring scroll position when user:
    // - clicked back button
    // - clicked on a link that programmatically calls `history.goBack()`
    // - manually changed the URL in the address bar (here we might want
    // to scroll to top, but we can't differentiate it from the others)
    if (location.action === 'POP') {
      return;
    }
    // In all other cases, scroll to top
    window.scrollTo(0, 0);
  });
});

var routes = (
  <Router history={history}>
    // ...
  </Router>
);

Here's my slightly modified hack to scroll to a URL fragment if it exists

history.listen(location => {
  // Use setTimeout to make sure this runs after React Router's own listener
  setTimeout(() => {
    // Keep default behavior of restoring scroll position when user:
    // - clicked back button
    // - clicked on a link that programmatically calls `history.goBack()`
    // - manually changed the URL in the address bar (here we might want
    // to scroll to top, but we can't differentiate it from the others)
    if (location.action === 'POP') {
      return;
    }
    // In all other cases, check fragment/scroll to top
    var hash = window.location.hash;
    if (hash) {
      var element = document.querySelector(hash);
      if (element) {
        element.scrollIntoView({block: 'start', behavior: 'smooth'});
      }
    } else {
     window.scrollTo(0, 0);
    }
  });
});

@kjs3 We're actively discussing this on #2471 and the associated issue. Would appreciate your feedback there since you've obviously put some thought into this.

@taion Ok cool, I'll test it out and let you know over there.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexyaseen picture alexyaseen  路  3Comments

maier-stefan picture maier-stefan  路  3Comments

nicolashery picture nicolashery  路  3Comments

misterwilliam picture misterwilliam  路  3Comments

Radivarig picture Radivarig  路  3Comments