3

I'm using locomotive-scroll with Next.js and all working fine. But after route to a different page, my scroll won't destroy and 2 scrolls overlap each other.

How to correctly reinit locomotive-scroll in Next.js after route?

My code example:

function MyApp({ Component, pageProps }) {
    useEffect(() => {
        import("locomotive-scroll").then((locomotiveModule) => {
            let scroll = new locomotiveModule.default({
                el: document.querySelector("[data-scroll-container]"),
                smooth: true,
                smoothMobile: false,
                resetNativeScroll: true,
             });
          
             scroll.destroy();  //<-- DOESN'T WORK OR IDK
    
             setTimeout(function () {
                 scroll.init();
             }, 400);
         });
     });
    
     return (
         <main data-scroll-container>
             <Component {...pageProps} />
         </main>
     );
}
juliomalves
  • 42,130
  • 20
  • 150
  • 146
  • Try moving the `scroll.destroy` call to the return statement ([cleanup phase](https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1)) of the `useEffect`, i.e., `return () => scroll.destroy();`. – juliomalves Jul 06 '21 at 18:37
  • still doesn't work, return () => { scroll.destroy(); setTimeout(function () { scroll.init(); }, 500); }; – Denis Kunitsyn Jul 07 '21 at 04:16
  • I think the problem is in the dynamic import module locomotive, it initializes a new module and applies scripts to it already, and the old remains unchanged, perhaps need to replace the dynamic import static – Denis Kunitsyn Jul 07 '21 at 04:32

2 Answers2

9

You should move the scroll.destroy call to the cleanup phase of the useEffect. You also don't need to explicitly call scroll.init().

function MyApp({ Component, pageProps }) {
    useEffect(() => {
        let scroll;
        import("locomotive-scroll").then((locomotiveModule) => {
            scroll = new locomotiveModule.default({
                el: document.querySelector("[data-scroll-container]"),
                smooth: true,
                smoothMobile: false,
                resetNativeScroll: true
            });
        });

        // `useEffect`'s cleanup phase
        return () => {
            if (scroll) scroll.destroy();
        }
    });

    return (
        <main className="main" data-scroll-container>
            <Layout>
                <Component {...pageProps} />
            </Layout>
        </main>
    );
}
juliomalves
  • 42,130
  • 20
  • 150
  • 146
  • If I do set it up like this, how can I pass `scroll` down to a child component (e.g. header with anchors, where I'm using `scroll.scrollTo(anchorTarget)`)? – inzn Jan 07 '23 at 10:10
4

everything @juliomalves said is true. but i just wanted to add one point. Locomotive works by initialising itself and reading the actual document size on page load. but the situation with next js routing is that the page loads only once, after that its only components changes, no actual page reloading. so the Scroll instance will assume the page is still the same size on load. this leads to a lot of bugs and page breakage.

the way i solved it is by adding this line at the app component: useEffect(()=> window.dispatchEvent(new Event('resize')), [Component])

this way the resize event will trigger every time the page "component" is switched. the locomotive Scroll instance will pick up this event and recalculate the page size according to the new component size.