locomotive-scroll icon indicating copy to clipboard operation
locomotive-scroll copied to clipboard

Usage with React

Open der-lukas opened this issue 5 years ago β€’ 83 comments

Is there a way of using this library with React? How would I initialize and target components? Would be awesome if you could supply a minimal setup example!

Thank you!

der-lukas avatar Jul 26 '19 10:07 der-lukas

Also interested in an example

vsanta avatar Jul 26 '19 15:07 vsanta

I guess something like this: https://codesandbox.io/embed/locomotive-scroll-in-react-k0028.

It's throwing some console errors though but I just put this together really quickly. I've just attached a ref to the container and the I fire the script via the useEffect hook so it fires when the component is mounted.

nathobson avatar Jul 27 '19 11:07 nathobson

@nathobson this doesn't work. Its not smooth.

jaexplorer avatar Sep 26 '19 23:09 jaexplorer

I use a similar approach as @nathobson to get the element with aref, but I use class components and initiate locomotiveScroll in componentDidMount.

https://codesandbox.io/s/proud-resonance-ou1rz

The main thing to just be aware of when using it with react is if you unmount the component that has rendered the DOM el used by locomotiveScroll, you'll need to destroy your locomotive scroll instance (i think in componentWillUnmount), otherwise you will probably get a bunch of errors.

adamcoulombe avatar Oct 25 '19 14:10 adamcoulombe

Hey, I'm trying to use the module using next.js, and I'm having trouble importing locomotiveScroll. I'm getting an error similar to the issue people had for nuxt.js, document is not defined, but somehow I couldn't find a way to make it work.

cytronn avatar Oct 28 '19 17:10 cytronn

@cytronn You'll probably need to dynamically import the module so it's not processed server-side (no document there).

millette avatar Oct 28 '19 21:10 millette

@adamcoulombe how do you destroy it on componentWillUnmount?

aaadamgo avatar Nov 09 '19 16:11 aaadamgo

@aaadamgo i guess use the destroy method

componentWillUnmount(){
myScroller.destroy()
}

adamcoulombe avatar Nov 11 '19 01:11 adamcoulombe

@aaadamgo i guess use the destroy method

componentWillUnmount(){
myScroller.destroy()
}

Thanks Adam, i tried that, or similar anyway. And when it changes page, if i go back to the page where I'm using it, something weird happens where it's likes there's 2 versions of the scrollbar.

see here:

export default class IndexPage extends React.Component {
    constructor(props) {
        super(props);
        const lsRef = null;
    }

    render() {
        return (
            <Animation transitionStatus={this.props.transitionStatus}>
                <div className="ls">
                    ---
                    <Projects />
                </div>
            </Animation>
        );
    }

    componentDidMount() {
        this.lsRef = new LocomotiveScroll({
            el: document.querySelector(".ls"),
            smooth: true,
        });
        window.addEventListener("resize", () => {});
    }

    componentWillUnmount() {
        this.lsRef.destroy();
    }
}

aaadamgo avatar Nov 11 '19 09:11 aaadamgo

It’s possible that the destroy method only cleans up the events.

Take a look at the source:

https://github.com/locomotivemtl/locomotive-scroll/blob/4240f83c01d57b60256189ad5960163c3ec5e197/src/scripts/Core.js#L211

Because of this you may also need to manually remove elements that were added like the scrollbar, using some vanilla JS to clean up DOM that locomotive is leaving behind.

Seems like locomotive should be doing this when you destroy- may be worth a separate issue/feature request- unless I’m missing something?

adamcoulombe avatar Nov 11 '19 11:11 adamcoulombe

Hello. I am using Next.js and just did what @millette said to dynamically import the module. The error is gone (document is not defined) but still doesn't work. Did I miss something? Here's my code:

import React from "react";
import dynamic from "next/dynamic";

const LocomotiveScroll = dynamic(() => import("locomotive-scroll"), {
  ssr: false
});

class Index extends React.Component {
   componentDidMount() {
    const scroll = new LocomotiveScroll({
      el: document.querySelector(".app"),
      smooth: true
    });
    console.log(scroll);
  }

 render() {
    return (
      <>
        <div className="app">
        ....
        </div>
     </>
     );
  }
}

export default Index;

Thanks in advance!

fukou avatar Nov 23 '19 04:11 fukou

Has anyone managed to get this to destroy completely.

@adamcoulombe i think i'm experiencing what you stated, when i leave the page with the locomotive-scroll on i'm calling destroy in a react hook but its not removing everything, which is fine on the page i go to but sometimes if i go back to a page with locomotive on it fucks up.

here is an example https://pup.adamwright90.now.sh/

adamjw3 avatar Jan 22 '20 20:01 adamjw3

Has anyone managed to get this to destroy completely.

@adamcoulombe i think i'm experiencing what you stated, when i leave the page with the locomotive-scroll on i'm calling destroy in a react hook but its not removing everything, which is fine on the page i go to but sometimes if i go back to a page with locomotive on it fucks up.

here is an example https://pup.adamwright90.now.sh/

Yeah, if you look at the code in the destroy function, it removes event listeners, but does no DOM cleanup whatsoever, which will likely cause issues

adamcoulombe avatar Jan 28 '20 17:01 adamcoulombe

Hi, when I try to use Locomotive Scroll with React, it seems I can't see all my content. But if I resize the window, it works normally. Does someone have this issue too ? Thx, Laurie

LaurieVince avatar Feb 18 '20 13:02 LaurieVince

@LaurieVince are you initializing the scroll in the componentDidMount lifecycle method?

elebumm avatar Feb 20 '20 02:02 elebumm

Sure, I do.

componentDidMount(){
const scroll = new LocomotiveScroll({
el: document.querySelector('.scroll'),
smooth: true
})
}

I note that it works perfectly when smooth is false. But when it's true, I can't see all my content. That's weird! Does someone else have this issue ?

LaurieVince avatar Feb 20 '20 11:02 LaurieVince

Hi Laurie,

As @Jerek0 said in #66, you need to use update(). But another alternative, when you're using React : You simply can use the setTimeout method to call your scroll, in componentDidMount. Like this :

scroll() {
  const scroll = new LocomotiveScroll({
    el: document.getElementById('#scroll'),
    smooth: true
  }); 
}

componentDidMount(){
  setTimeout(() => {
    this.scroll();
  }, 100);
}

I tested this, and it works perfectly for me.

CorentinBernadou avatar Feb 20 '20 15:02 CorentinBernadou

@imcorentin πŸ‘

If you have an incorrect scroll size (too long / short) at first state on your page: it probably comes from a layout change that occurred after LS init (e.g. an image loaded). Try resizing your window: it probably fixes the scroll.

To fix that for real, you need to call .update() on your LS instance after layout changes to refresh calculations.

In addition to that: 3.3.5 fixes an important issue with .update(). Make sure to upgrade!

Jerek0 avatar Feb 20 '20 18:02 Jerek0

@imcorentin Hey! With Gatsby though, I'd tried a timeout on component mount/useEffect as well as a helper onInitialClientRender which is triggered after the site is loaded, they didn't seem to be solving the issue. The workaround I found if the page has a lot of content loading is calling update() at regular intervals.

@Jerek0 ahh maybe that update could fix the issue, will test it and get back.

ajayns avatar Feb 21 '20 05:02 ajayns

Unfortunately, with the Gatsby case, it still seems like I have to call update() on intervals to ensure that the scroll works correctly for all kinds of pages.

setInterval(() => scroll.update(), 1000);

ajayns avatar Feb 21 '20 05:02 ajayns

Here is an updated example with 3.3.5 showing usage with react and using the update() method when the route changes. I did find that you need to call it after a short timeout (100) sort of like @imcorentin pointed out

https://codesandbox.io/s/icy-wood-96gvz?fontsize=14&hidenavigation=1&theme=dark&view=preview

adamcoulombe avatar Feb 21 '20 18:02 adamcoulombe

Unfortunately, with the Gatsby case, it still seems like I have to call update() on intervals to ensure that the scroll works correctly for all kinds of pages.

setInterval(() => scroll.update(), 1000);

The only thing about calling it repeatedly on interval, is that you have smooth:true, it may make it a little clunky if it updates mid-scroll, it would stop the lerping

adamcoulombe avatar Feb 21 '20 18:02 adamcoulombe

Hey everyone! I have some troubles when using scrollTo method. I have a main with data-scroll-section which contains 5 sections. Everything works great but when I fire a scrollTo the first section (main's first child), the main doesn't get the new position of the main, so i cannot scroll up after fire a scrollTo.

Also, I cannot access update() method due to react : I create the locomotiveScroll in a use effect.

Is someone have any idea? Many thanks :)

mehdilouraoui avatar Feb 25 '20 13:02 mehdilouraoui

Hey @mehdilouraoui, can you please create a Codesandbox to show us your issue with the scrollTo method?

CorentinBernadou avatar Feb 25 '20 13:02 CorentinBernadou

Hey @imcorentin I just recreate it (very simpler than the current one in my project): https://codesandbox.io/s/determined-lehmann-w5876

And it works... but not in my project. I attach useState hook to GridInterior component just because I fetch content when the component is mounted.

However, instead of attach the scrollTo to a forwardRef, i just attach it to a div which wrap the component, but nothing works.

I'm not sure this is pretty clear, I just have to investigate more in my project.

mehdilouraoui avatar Feb 25 '20 13:02 mehdilouraoui

Unfortunately, with the Gatsby case, it still seems like I have to call update() on intervals to ensure that the scroll works correctly for all kinds of pages.

setInterval(() => scroll.update(), 1000);

I had this issue too and also ran into the issue that @adamcoulombe addressed from your reply.

Depending on what kind of site you have, this might work (it did for me). I was able to insert this scroll method into the componentDidMount lifecycle method and then added a slight delay to the update method.

componentDidMount() {
    this.scroll = new LocomotiveScroll({
      el: document.querySelector("body"),
      smooth: true,
    })
    setTimeout(() => this.scroll.update(), 300);
}

I then just destroy the scroll instance and do it on separate pages. If you have dynamic content coming in, (maybe a table that is generated through an AJAX request) this might not work for you, but it worked for me.

elebumm avatar Feb 25 '20 19:02 elebumm

Hey @elebumm ! Yes it's a solution but I don't want to fix with a timeout because as you said, I fetch content from an API. I'm currently trying to update the locomotive scroll (with a boolean in a state) once the content is fully loaded. Should works

mehdilouraoui avatar Feb 26 '20 08:02 mehdilouraoui

How to implement it in Gatsby ?

Adarsh777 avatar Mar 20 '20 10:03 Adarsh777

Hello all,

Thank you for this feed :) Regarding LM on Wordpress I did this:

At the end of my preloader I call a callback with an onComplete: scroll.update ();

To always have the right height, I had woocommerce pages that scrolled the content after my footer.

And during the transition with barbajs I destroy LM at hooks.beforeLeave and I reinit it at the entrance of the new DOM .hooks.after

dngraphisme avatar Apr 15 '20 06:04 dngraphisme

Hi, I currently have Locomotive Scroll working in Gatsby, I have some snippets down below.

But I'm posting on here regarding the data-scroll-call and more specifically to figure out how to use it with Gatsby.

I basically init the library on the client and store the instance in window. I can then simply do stuff like updating the scroll by calling () => typeof window !== 'undefined' && window.scroll.update().

Here's my issue, when trying to call an event inside a component:

window.scroll.on('call', func => {
    func === 'testCall' && console.log('Event Called')
})

I get an error ⚠️ TypeError: "window.scroll.on is not a function"

Any pointers?


My working Gatsby config

import LocomotiveScroll from 'locomotive-scroll'

//

useEffect(() => {
    let locomotiveScroll
    locomotiveScroll = new LocomotiveScroll({
      el: document.querySelector('#___gatsby),
      ...scroll.options,
    })
    locomotiveScroll.update()

    // Exposing to the global scope
    window.scroll = locomotiveScroll

    return () => {
      if (locomotiveScroll) locomotiveScroll.destroy()
    }
 }, [location])
// Callback on routeChange

In gatsby-node.js

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === 'build-html' || stage === 'develop-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /locomotive-scroll/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}

πŸŽ‰ Now it works and you can reference the instance like so () => typeof window !== 'undefined' && window.scroll.update().

fcisio avatar Apr 24 '20 22:04 fcisio