I fetch a list of posts in index js like this :
const Index = (props) => {
return (
<div>
{props.posts.map((each) => {
return (
<Link scroll={false} as={`/post/${each.id}`} href="/post/[id]" key={each.id}>
<a>
<h1>{each.title}</h1>
</a>
</Link>
);
})}
</div>
);
};
export async function getStaticProps() {
const url = `https://jsonplaceholder.typicode.com/posts`;
const res = await axios.get(url);
return {
props: { posts: res.data }
};
}
And when user clicks on any link it goes to post page which is :
function post({ post }) {
return (
<h1>{post.id}</h1>
);
}
export async function getServerSideProps({ query }) {
const { id } = query;
const res = await Axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);
return {
props: { post: res.data}
};
}
The problem is when I click back the scroll position resets to top and it fetches all posts .
I included scroll={false}
in Link but it doesn't work .
How can I prevent scroll resetting when user clicks back from the post page ?
We can simply enable it by editing the next.config.js: module.exports = { experimental: { scrollRestoration: true, }, } hmm, this did not work for me. scroll= {false} doesn't maintain the scroll of the previous page; it doesn't change the scroll at all which means the scroll would be that of the page you are linking from.
Definition and Usage. The scroll-behavior property specifies whether to smoothly animate the scroll position, instead of a straight jump, when the user clicks on a link within a scrollable box. Default value: auto. Inherited:
Next.js in fact has built-in support for restoring scroll position when back to the previous page. We can simply enable it by editing the next.config.js: module.exports = { experimental: { scrollRestoration: true, }, }
Then, in your _app.js component, wrap your pages with the UserContext and create a useRef attribute to store the scroll position.
Next.js in fact has built-in support for restoring scroll position when back to the previous page. We can simply enable it by editing the next.config.js
:
module.exports = {
experimental: {
scrollRestoration: true,
},
}
scroll={false}
doesn't maintain the scroll of the previous page; it doesn't change the scroll at all which means the scroll would be that of the page you are linking from. You can use scroll={false}
to override the default behavior of setting the scrollY to 0, so you can implement your own behavior.
Here's how I implemented restoring the scroll position. This is very similar to Max william's answer, but using useRef
instead of useState
. We are using useRef
instead of useState
because useRef
does not cause a re-render whenever its value is mutated, unlike useState
. We are going to be updating the value to the current scroll position every time the scroll is changed by the user, which would mean a ton of useless re-renders if we were to use useState.
First, define a UserContext component to easily pass the scroll data from the _app.js component to wherever you need:
import { createContext } from 'react';
const UserContext = createContext();
export default UserContext;
Then, in your _app.js component, wrap your pages with the UserContext and create a useRef attribute to store the scroll position.
import { useRef } from 'react';
import UserContext from '../components/context'
function MyApp({ Component, pageProps }) {
const scrollRef = useRef({
scrollPos: 0
});
return (
<Layout>
<UserContext.Provider value={{ scrollRef: scrollRef }}>
<Component {...pageProps} />
</UserContext.Provider>
</Layout>
)
}
export default MyApp
Then inside whichever page component you are wanting to restore the scroll position (that is, the page that you want to return to and see the same scroll position as when you left), you can put this code to set the scroll position of the page and bind a scroll event to a function to update the stored scroll position.
import UserContext from '../components/context'
import { useContext } from 'react';
export default function YourPageComponent() {
const { scrollRef } = useContext(UserContext);
React.useEffect(() => {
//called when the component has been mounted, sets the scroll to the currently stored scroll position
window.scrollTo(0, scrollRef.current.scrollPos);
const handleScrollPos = () => {
//every time the window is scrolled, update the reference. This will not cause a re-render, meaning smooth uninterrupted scrolling.
scrollRef.current.scrollPos = window.scrollY
};
window.addEventListener('scroll', handleScrollPos);
return () => {
//remove event listener on unmount
window.removeEventListener('scroll', handleScrollPos);
};
});
return (
//your content
)
}
The last little thing is to use scroll={false}
on your Link component that links back to YourPageComponent. This is so next.js doesn't automatically set the scroll to 0, overriding everything we've done.
Credit to Max william's answer to the majority of the structure, my main change is using useRef. I also added some explanations, I hope it's helpful!
I solved the problem with the help of context and window scroll position like this :
import UserContext from '../context/context';
function MyApp({ Component, pageProps }) {
const [ scrollPos, setScrollPos ] = React.useState(0);
return (
<UserContext.Provider value={{ scrollPos: scrollPos, setScrollPos: setScrollPos }}>
<Component {...pageProps} />
</UserContext.Provider>
);
}
export default MyApp;
index js file :
import Link from 'next/link';
import UserContext from '../context/context';
import { useContext } from 'react';
import Axios from 'axios';
export default function Index(props) {
const { scrollPos, setScrollPos } = useContext(UserContext);
const handleScrollPos = () => {
setScrollPos(window.scrollY);
};
React.useEffect(() => {
window.scrollTo(0, scrollPos);
}, []);
React.useEffect(() => {
window.addEventListener('scroll', handleScrollPos);
return () => {
window.removeEventListener('scroll', handleScrollPos);
};
}, []);
if (props.err) {
return <h4>Error bro</h4>;
}
return (
<div>
{props.res.map((each) => {
return (
<div key={each.id}>
<Link scroll={true} as={`/post/${each.id}`} href="/post/[id]">
{each.title}
</Link>
</div>
);
})}
</div>
);
}
export async function getServerSideProps() {
let res;
let err;
try {
res = await Axios.get('https://jsonplaceholder.typicode.com/posts');
err = null;
} catch (e) {
err = 'Error bro';
res = { data: [] };
}
return {
props: {
res: res.data,
err: err
}
};
}
post js file :
import Axios from 'axios';
function Post(props) {
if (props.err) {
return <h4>{props.err}</h4>;
}
return <h1>{props.post.title}</h1>;
}
export async function getServerSideProps(ctx) {
const { query } = ctx;
let err;
let res;
try {
res = await Axios.get(`https://jsonplaceholder.typicode.com/posts/${query.id}`);
err = null;
} catch (e) {
res = { data: [] };
err = 'Error getting post';
}
return {
props: {
post: res.data,
err: err
}
};
}
export default Post;
So when you click back from post js page , the first useEffect in index js will run and you will be scrolled to that position .
Also after that the second useEffect will capture the user's scroll position by listening to the scroll event listener so it will always save the latest scroll y position in context so next time you comeback to index js the first useEffect will run and set scroll position to that value in context .
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With