Trying to have the browser scroll to the top upon cliking a router . Curren't it is preserving the scroll position.
Could not find anywhere in the documentation.
my render looks something like this
React.render(
<Router history={createBrowserHistory()} routes={routes} />,
document.getElementById('root')
);
There's been a lot of discussion in previously filed (and closed) issues on the topic of scroll behavior with 1.0. Check it out! :)
https://github.com/rackt/react-router/issues?utf8=%E2%9C%93&q=is%3Aissue+scroll
I'm on 1.0.0-rc1 and also need conventional browser scroll behaviour (preserve scroll position when using the back button, scroll up when you come to a new page).
Any chance you can give us an ETA for getting this into 1.0.0, or a quick-fix until? None of the closed issues matching 'scroll' give a hint..
+1
Use onUpdate, from #2144
<Router onUpdate={() => window.scrollTo(0, 0)} history={createBrowserHistory()}>
...
</Router>
@darreng see my comment on implementing the behavior "preserve scroll position when using the back button, scroll up when you come to a new page" https://github.com/rackt/react-router/issues/2144#issuecomment-150939358
Is the onUpdate={() => window.scrollTo(0, 0)}
and https://github.com/rackt/react-router/issues/2144#issuecomment-150939358 the proposed way to handle this in 1.0.0?
I have tried to read through all the issues but have not found anything that makes this clear.
how do I get the current location in the onUpdate() function?
@webular I don't think you can. I had the same need and switched from using onUpdate
to history's listen method (first argument is the location object): https://github.com/rackt/history/blob/master/docs/GettingStarted.md
@jackmoore thx
@webular if you're looking to preserve scroll depth for the back button, you can just use the window.location.
This really deserves a FAQ or a mention in the readme. I have read through a lot of issues and I have no idea what the current stance is on it.
I think there is still no mention on this. The current advice you find first is to use onUpdate={() => window.scrollTo(0, 0)}
which scrolls to the top on every transition. I don't think that is what most people want?
Maybe we can agree on a proposed way to do this and push it to the docs afterwards. You can llisten for route changes on history.listen
or history.listenBefore
. You can then easily "whitelist" the location changes you would like to use for window.scrollTo(0, 0)
.
let prevLocation = {};
history.listenBefore(location => {
const pathChanged = prevLocation.pathname !== location.pathname;
const hashChanged = prevLocation.hash !== location.hash;
if (pathChanged || hashChanged) window.scrollTo(0, 0);
prevLocation = location;
});
What do you guys think?
As documented in the upgrade guide, the best recommended solution right now is to use scroll-behavior, per https://github.com/reactjs/react-router/blob/master/upgrade-guides/v1.0.0.md#scrolling and https://github.com/reactjs/react-router/blob/master/upgrade-guides/v2.0.0.md#using-scroll-behavior.
We hope to eventually have a better solution, but this is the best one available at the moment.
In a real-world scenario, I found there is no common pattern that can be extrapolated - sometimes scroll-to-top is desired; sometimes it isn't. This method allows absolute control on a link-by-link basis: https://github.com/taion/scroll-behavior/issues/15#issuecomment-173746149
As you can see, the browser bug in question is getting fixed, and there is support for controlling when to update the scroll position on a transition-by-transition basis in scroll-behavior now.
@taion as we can see where? Where is any documentation for per-transition control in scroll-behaviour?
Browser issue noted https://github.com/taion/scroll-behavior/issues/15#issuecomment-198339485. Scroll suppression support was added in https://github.com/taion/scroll-behavior/pull/35, and I'd be happy to receive any contributions to the docs to document this feature.
Documented here now: https://github.com/taion/scroll-behavior#suppressing-scroll-updates
As I understand the new documentation, it is still not possible to control the transition on a case-by-case basis - the same rule will be used for all transitions from /a to /b.
You have access to all the location data you need, including anything you might want to set on location.state
to specifically identify the transition, as well as the ability to close over local state in the callback (though you should probably not do that).
@taion thanks for the solution! It works for me :)
@taion is there a way to delay the scroll update until the next page has mounted? We're seeing an issue where the scroll position updates on the current page because the next page takes a second or so to load.
Use your top level route component to scroll on location change in
componentDidUpdate?
Mike
On Fri, Apr 29, 2016 at 5:40 PM, Brandon Dail [email protected]
wrote:
@taion https://github.com/taion is there a way to delay the scroll
update until the next page has mounted? We're seeing an issue where the
scroll position updates on the current page because the next page takes a
second or so to load.—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
https://github.com/reactjs/react-router/issues/2019#issuecomment-215888868
@mikestopcontinues that would break the behavior we want, which is scroll-behavior's useStandardScroll
. We want to track scroll position for POP
events.
@koichik added it to scroll-behavior in https://github.com/taion/scroll-behavior/pull/58 but I haven't had a chance to write a proper router integration yet.
now you can use react-router-scroll
As noted above, please use react-router-scroll if you need this.
I don't think it's fully ready to be pulled in here, but it's good enough for general use.
...
function handleUpdate() {
let {
action
} = this.state.location;
if (action === 'PUSH') {
window.scrollTo(0, 0);
}
}
...
<Router
onUpdate={handleUpdate}
/>
...
Worked pretty well for me, so thought I'd share :sun_with_face:
Here is my solution for react-router 2.8.1 version :) In this version we preserve scroll location only if user goes back one route (POP) otherwise scroll to top.
import React from "react";
import { Route } from "react-router";
import App from "components/App";
import Profile from "components/Profile";
const routes = (
<Route
component={App}
path="/"
onChange={(prevState, nextState) => {
if (nextState.location.action !== "POP") {
window.scrollTo(0, 0);
}
}}
>
<Route path="profile" component={Profile} />
// ...
// more routes here
</Route>
);
export default routes;
how do you solve this in react-router v4?
In addition to @FoxWhite in v4, I tried the follow:
<Match pattern="*" compoent={ScrollToTop} />
const ScrollToTop = () => {
window.scrollTo(0, 0);
return null;
}
But the side affect of this is if you have <Miss>
it will never be called.
Any suggestions?
Edit: I want to avoid the need of creating <MyMatch>
that will do it in componentDidMount
,
it'd be nice to have a callback prop to pass on Router, like onUpdate
I've solved it this way (for v4 router):
import React, { Component } from 'react'
import { Match, propTypes as routerPropTypes } from 'react-router'
export class SomeRootComponent extends Component {
static contextTypes = {
history: routerPropTypes.historyContext.isRequired,
}
componentDidMount() {
const { context } = this
this.historyUnlisten = context.history.listen(() => this.scrollToTop())
}
componentWillUnmount() {
this.historyUnlisten()
}
scrollToTop() {
// In fact here I have some more complicated logic for smooth animation,
// but this one is ok too
window.scrollTo(0, 0)
}
render() {
return <Match pattern="/" component={SomeComponent} />
}
}
Another iteration similar to @antipin's v4 solution, but avoiding reading from context
directly by using withRouter
to look for location
changes
export class RouteHandler extends React.Component {
componentWillReceiveProps(nextProps) {
const { location, history: { action } } = nextProps;
if (location !== this.props.location && action === 'PUSH') {
// new navigation - scroll to top
window.scrollTo(0, 0);
}
// eventually we might want to try setting up some scroll logic for 'POP'
// events (back button) to re-set the previous scroll position
}
render() {
return this.props.children;
}
}
export default withRouter(RouteHandler);
The solution that works for me is just add <Route>
at the top level component:
const App = () => {
return (
<BrowserRouter>
<Route component={ScrollToTop} />
{/*
rest of the routes definition
<Switch>, more <Route>, etc ....
*/}
</BrowserRouter>
);
}
const ScrollToTop = () => {
window.scrollTo(0, 0);
return null;
};
@idanwe Your solution gives me the following error: "A
I have one
@mmcgahan You solution won't work robustly
both the back and forward buttons in a web browser trigger a POP action
Let's say the navigation stack is
-> A -> B
Then we press go back, POP
-> A
navigate somewhere deep in page A
Then we press go forward, still POP
action
-> A -> B
We will end up with some deep down in page B also
Here's a naively implemented solution. It works for me so far.
Do note that there are browser discrepancies not handled (like browser fire on scroll event for push state change etc)
// @flow
import React, { Component, Children } from 'react';
import { withRouter } from 'react-router';
import type { Location, RouterHistory } from 'react-router';
type PropTypes = {
location: Location,
history: RouterHistory,
children: any,
};
const PREFIX = '@@scroll/';
class ScrollBehavior extends Component {
props: PropTypes;
static locationVisited: {
[key: string]: number,
} = {};
constructor(props: PropTypes) {
super(props);
(this: any).onScroll = this.onScroll.bind(this);
}
componentDidMount() {
history.scrollRestoration = 'manual';
window.addEventListener('scroll', this.onScroll);
}
componentWillUnmount() {
history.scrollRestoration = 'auto';
window.removeEventListener('scroll', this.onScroll);
}
onScroll() {
requestAnimationFrame(() => {
ScrollBehavior.locationVisited[this.getLocationKey(this.props.location.key)] = window.scrollY;
});
}
getLocationKey(key) {
return PREFIX + (key || '');
}
scrollTo(Y: number) {
requestAnimationFrame(() => {
window.scrollTo(0, Y);
});
}
componentWillReceiveProps(nextProps: PropTypes) {
const { location, history: { action } } = nextProps;
if (location !== this.props.location) {
if (action === 'PUSH') {
// new navigation - scroll to top
this.scrollTo(0);
} else {
const key = this.getLocationKey(location.key);
if (ScrollBehavior.locationVisited.hasOwnProperty(key)) {
const scrollY = ScrollBehavior.locationVisited[key];
this.scrollTo(scrollY);
}
}
}
}
render() {
return Children.only(this.props.children);
}
}
export default withRouter(ScrollBehavior);
I ended up using the <ScrollToTop>
workaround described in the react-router docs.
Here is my implementation, and an example usage (Typescript).
Most helpful comment
Use onUpdate, from #2144