Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change scroll behavior while going back in next js?

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 ?

like image 716
Mahdi Faraji Avatar asked Sep 24 '20 18:09

Mahdi Faraji


People also ask

How to enable scrollrestoration in jQuery?

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.

What is scroll behavior in HTML?

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:

How to restore scroll position when back to the previous page?

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, }, }

How to get the scroll position of the page in JavaScript?

Then, in your _app.js component, wrap your pages with the UserContext and create a useRef attribute to store the scroll position.


3 Answers

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,
  },
}
like image 171
Devo Avatar answered Nov 15 '22 03:11

Devo


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!

like image 33
Miles Avatar answered Nov 15 '22 03:11

Miles


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 .

like image 33
Mahdi Faraji Avatar answered Nov 15 '22 03:11

Mahdi Faraji