-
Notifications
You must be signed in to change notification settings - Fork 27.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Link component does not persist scroll position #1309
Comments
It might have to do with the latency of getting the data? I've been wanting to experiment with passing |
Hmm, do you mean when using prefetch? I tried both with and without prefetch and the issue was still there. I'm not sure how data latency might affect the scroll position of the viewport when going back a page. Do you mind explaining? |
I was assuming that the browser has a heuristic for how long to wait for a scroll height to become available to scroll to. |
Because, keep in mind that the popstate event is optimistic. We can't block the location from changing, it changes immediately (this is different from how push is handled). Therefore, you press back, we start fetching data, that might take N milliseconds. I was assuming that browsers give up on preserving scroll if that N is sufficiently large. |
Ah, I see. That sounds reasonable. What is the difference between clicking the back button or refreshing the page, though? Does clicking the back trigger the History API and you listen for that in order to fetch the page's data? When the location changes immediately does it do another render server side, or is it all taken care of in the client? If it's done in the client then perhaps the browser might try to scroll before React mounts the component tree (when the viewport has no height). Which would make sense then that when you do a full refresh it works (the full refresh goes to the server and comes back with the entire markup). |
I just took a look at what was going on behind the scenes and it does look that there are no network requests (other than static assets) when going back a page. This definitely sounds like it would make everything go much faster but it comes at the cost of losing the scroll position. Is there any way to avoid this? |
At the time we always fetch data fresh. I'm thinking that if we expose said event to export default withPopstateCache(class extends React.Component {
static async getInitialProps () {
cont res = await fetch('api.com/this/will/be/cached/when/you/press/back')
return res.json()
}
}) but anyway, my rant could just be unrelated to the scroll position bug :P |
Haha, I think that might work. Maybe also a way to simply skip the History API? So perhaps a way to not listen for the browser's back button and allow to set this as an option? |
Does anyone know why is it that when I added in this code the scroll position retains when I go from page1 to page2 and then click the back button: componentDidMount () { |
@stacygohyunsi You can use |
@vinaypuppal is there a way to achieve this while using router.push? |
@coluccini Yes
|
Oh, I didn't know that router return a promise :) Thanks! |
Currently this is not implemented with Link. But if someone could send a PR I'd happy to take it. May be someone could create a package like: |
@migueloller I don't think I quite understood the solution @rauchg suggested. The premise is to cache the data on client-side to expedite rendering speed? Did you ever figure out a solution? Also, @arunoda, you suggested someone could make a PR. Do you have any pointers as to which files or folders I should look at? I would love to make that contribution. Currently, this is how I'm persisting scroll position with a route listener instance:
This function is called when I bootstrap client. This only works sometimes because data may take too long to load and the browser will scroll to top by default. I would like a way to persist the scroll position without having to optimizing my render function (I mean I could, but I want to know if there's a functional way of solving this issue). |
@sojungko I think it's better to implement this as a user package. What you've done is correct. You can also listen to router events like this: https://github.com/zeit/next.js/blob/canary/client/on-demand-entries-client.js#L8 That's a best suited method for a package like this. |
Hi @sojungko , can you please tell me where I should execute that function? EDIT: I think I found out, that the place to run that code is the Also, did you find a better solution? Thanks! |
@arunoda About your last comment, how do we know in Or, is there a better way to handle this issue now? |
@fmaylinch did you find the right way to solve this? i m struggling with this problem too since several days. Any help will be greatly appreciated. |
Sorry @karanmartian, I saw your message and forgot to reply. Today I found your message again. I am not working on the project now, but I did this hack to solve it. This code is in my export default class MyApp extends App {
....
}
initRouterListeners();
function initRouterListeners() {
console.log("Init router listeners");
const routes = [];
Router.events.on('routeChangeStart', (url) => {
pushCurrentRouteInfo();
});
Router.events.on('routeChangeComplete', (url) => {
fixScrollPosition();
});
// Hack to set scrollTop because of this issue:
// - https://github.com/zeit/next.js/issues/1309
// - https://github.com/zeit/next.js/issues/3303
function pushCurrentRouteInfo() {
routes.push({pathname: Router.pathname, scrollY: window.scrollY});
}
// TODO: We guess we're going back, but there must be a better way
// https://github.com/zeit/next.js/issues/1309#issuecomment-435057091
function isBack() {
return routes.length >= 2 && Router.pathname === routes[routes.length - 2].pathname;
}
function fixScrollPosition () {
let scrollY = 0;
if (isBack()) {
routes.pop(); // route where we come from
const targetRoute = routes.pop(); // route where we return
scrollY = targetRoute.scrollY; // scrollY we had before
}
console.log("Scrolling to", scrollY);
window.requestAnimationFrame(() => window.scrollTo(0, scrollY));
console.log("routes now:", routes);
}
} |
Hi, I hope this helps someone. My requirements:
My solution: // inside _app.tsx
scrollPositionRestorer()
function scrollPositionRestorer() {
const scrollMemories: { [asPath: string]: number } = {};
let isPop = false;
if (process.browser) {
window.history.scrollRestoration = 'manual';
window.onpopstate = () => {
isPop = true;
};
}
Router.events.on('routeChangeStart', () => {
saveScroll();
});
Router.events.on('routeChangeComplete', () => {
if (isPop) {
restoreScroll();
isPop = false;
} else {
scrollToTop();
}
});
function saveScroll() {
scrollMemories[Router.asPath] = window.scrollY;
}
function restoreScroll() {
const prevScrollY = scrollMemories[Router.asPath];
if (prevScrollY !== undefined) {
window.requestAnimationFrame(() => window.scrollTo(0, prevScrollY));
}
}
function scrollToTop() {
window.requestAnimationFrame(() => window.scrollTo(0, 0));
}
} |
This comment has been minimized.
This comment has been minimized.
This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
When using
Link
for routing and then pressing the browser's back button, the page will not remember the previous scroll position.For example, go from
page1
topage2
and then click the back button.page1
is rendered at the top of the viewport.The browser is keeping track of the scroll position because if the page is refreshed it will refresh at the appropriate position. Is this an issue with the History API?
The text was updated successfully, but these errors were encountered: