Jump to content
GreenSock

Tonycre8

ScrollTrigger breaks React Router?

Recommended Posts

Hello GSAP gang, I have a little bit of a situation going on.

I understand that there's already a post on this topic, but I need help with this one as well.

I've got some code that I have tried out but it seems that I've been unable to fix this issue. The coding sandbox comes up with an error: "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node."
https://codesandbox.io/s/react-router-re-render-8btmz?file=/src/index.js

I was wondering if any of you guys had any experience in this, and how to get it so that the React router link works as intended?

Thanks

Link to comment
Share on other sites

Hi @Tonycre8,

 

I didn't have a change to dive into this, yet, but at a glance: Make sure you're doing garbage cleanup on your ScrollTrigger too (https://greensock.com/docs/v3/Plugins/ScrollTrigger/kill()), via ScrollTrigger.getById('yourID').kill();. Also, you're still using the old gsap2 syntax (you not longer need to use TimelineLite, etc) you can now use cont slide = gsap.timeline();

 

  • Like 1
Link to comment
Share on other sites

Hey there. Thanks for the response.

10 minutes ago, elegantseagulls said:

Hi @Tonycre8,

 

I didn't have a change to dive into this, yet, but at a glance: Make sure you're doing garbage cleanup on your ScrollTrigger too (https://greensock.com/docs/v3/Plugins/ScrollTrigger/kill()), via ScrollTrigger.getById('yourID').kill();. Also, you're still using the old gsap2 syntax (you not longer need to use TimelineLite, etc) you can now use cont slide = gsap.timeline();

 

I'll come back to updating the syntax in a short while, but about the potential solution.

What would the ID be? I've tried using this as well as the other bits of cleanup I've done.

ScrollTrigger.kill() 

in my code since then, but it hasn't seemed to work either.

Link to comment
Share on other sites

You'll want to set a unique id for your ScrollTrigger.

 

ScrollTrigger.create({
  id: 'slideID',
  ...
})

 

  • Like 2
Link to comment
Share on other sites

We seemed to have found a fix! Well, my brother worked on it whilst I was waiting for someone to turn up for a meeting.

Based on a comment from here: 

We found that the issue is actually just adding a surrounding div to my home.js code, so something like this:
 

return (
    <div>
      <div className="sections">
        <section className="section" style={{ background: "white" }}>
          <div>
            <Link to="/blog">Link</Link>
          </div>
        </section>
        <section
          className="section"
          id="section2"
          style={{ background: "green" }}
        >
          <p>Section 2</p>
        </section>
        <section
          className="section"
          id="section3"
          style={{ background: "blue" }}
        >
          <p>Section 3</p>
        </section>
        <section
          className="section"
          id="section4"
          style={{ background: "orange" }}
        >
          <p>Section 4</p>
        </section>
        <section
          className="section"
          id="section5"
          style={{ background: "yellow" }}
        >
          <p>Section 5</p>
        </section>
      </div>
    </div>
  );

Although we are still unsure of why this works, it seems to be a working solution.

Link to comment
Share on other sites

  • 2 weeks later...

Hi @Tonycre8

 

GSAP needs to do some cleanup before the component gets removed from the DOM, so you should create your scroll triggers inside useLayoutEffect like this. There should be no need to add an extra div using this method.

 

useLayoutEffect(() => {
  
  let st = ScrollTrigger.create({
    pin: true,
    ...
  });

  return () => {
    st.kill();
  };
}, []);

 

 

 

  • Like 2
  • Thanks 2
Link to comment
Share on other sites

  • 4 months later...

Are there any demos on this? Cannot seem to get it to work. I am killing the ST and can see they regenerate. Normal animations work but the issue seems to be with pinning. 

Link to comment
Share on other sites

Welcome to the forums @spirconi

 

Can you make a demo on CodeSandbox showing the problem? And did you try useLayoutEffect and make sure the component you are pinning is not fragment?

 

 

Link to comment
Share on other sites

16 minutes ago, OSUblake said:

Welcome to the forums @spirconi

 

Can you make a demo on CodeSandbox showing the problem? And did you try useLayoutEffect and make sure the component you are pinning is not fragment?

 

 

I have not tried to make a demo yet, I was hoping one existed I could try to work it out from first. 

 

Actually, I tried useLayoutEffect, and whilst it did not work, it may provide a clue. 

 

With useEffect, the pins work fine on initial load. The issue is when i go to / come back from another page it just ignores the pinned scrolltriggers. I notice the markers work as expected. I can see the top marker stay in place, which is met by the element start marker. When i come back to the page, the markers are in the same place but are just ignored. 

 

I noticed with useLayout effect the top marker moves up the page, so the pin never triggers. So that didnt work. 

 

I am using locomotive scroll setup (all works as expected minus this issue). I am suspecting it may be something to do with that setup up rather than gsap specifically. 

Link to comment
Share on other sites

I just noticed if i resize the window it works (with useEffect), maybe that will flag something that might be the cause?

Link to comment
Share on other sites

Ok, I added the following to the parent component which seems to have done the trick. 

 

useEffect(() => {
ScrollTrigger.refresh();
}, [ScrollTrigger.getAll()]);
Link to comment
Share on other sites

Just be careful with that code. It's going to call refresh every time there is a re-render. You might want to add a console.log in there just to test and make sure it's not being called a lot.

Link to comment
Share on other sites

On 10/23/2021 at 12:34 AM, spirconi said:

Ok, I added the following to the parent component which seems to have done the trick. 

 

useEffect(() => {
ScrollTrigger.refresh();
}, [ScrollTrigger.getAll()]);

Here to report that I had the exactly same issue and this code solved it. Navigating to another page and back breaks scroll triggers.

Link to comment
Share on other sites

1 hour ago, hereandnow said:

Navigating to another page and back breaks scroll triggers.

 

Are you still having trouble?

 

Just an FYI about the code posted above. It will run on every render and is basically the same as having an effect with no dependencies .

 

// has the same effect
useEffect(() => {
  ScrollTrigger.refresh();
}, [ScrollTrigger.getAll()]);

// as this
useEffect(() => {
  ScrollTrigger.refresh();
})

 

This is due to way React detects changes using Object.is. 

let same = Object.is(ScrollTrigger.getAll(), ScrollTrigger.getAll())

console.log("SAME", same) // false

 

 

Link to comment
Share on other sites

In my instance it is a very light app and only re rerenders twice as there is not much interactivity. I needed a fast solution to get the site live, but noted this is not recommended and will look for a better solution once I have chance to go back to it. 

  • Like 1
Link to comment
Share on other sites

Hi guys. 

 

I can report having the same error on route change of a Next.js app (Next Router, React 17.0.2, Next 11.1.2, GSAP 3.8.0).

It doesn't occur on a forced reload, just when navigating away from the page via Next router.

 

If I removed the Pinning, error went away. Note, I was using Refs for all dom targeting, and a custom useLayoutEffect hook I wrote for Next (which only runs Isomorphically). The hook had cleanup deps defined and killing ScrollTrigger on unmount didn't prevent the error.

 

In the end, the fix was to ensure the target ref was NOT on the component's parent element. The target ref seems to need a wrapper / parent so the injected pin spacers are actually child elements of the component. 

 

Interestingly, this also improved render times when navigating to the page. 

 

Still investigating specifics.

 

 

 

 

 

 

  • Like 2
  • Thanks 2
Link to comment
Share on other sites

Hi @stephens

 

Do you think you can put what you did in a CodeSandbox so others can see?

  • Like 1
Link to comment
Share on other sites

  • 2 months later...

Wrapping the scrolltrigger pin in a div didn't work for me as it seemed to change the scrolltrigger progress values?
Killing scrolltrigger on useEffect/useLayoutEffect unmount also didn't work.
What worked was manually triggering kill() before unmounting the component. 
So something like this...
 

    const backBtnPress = () => {
        animationsRef.current.menuFadeIn.vars.onReverseComplete = () => {
            sTrigger.current.kill();
            setNav('more')
        }
        animationsRef.current.menuFadeIn.reverse()
    }

 

  • Like 1
Link to comment
Share on other sites

  • 3 months later...

Using VueJS, adding the following to the router fixed the issue:
 

router.beforeEach((to, from) => {
  if (from.path !== to.path) ScrollTrigger.getAll().forEach(el => el.kill())
  return true
})

router.afterEach((to, from) => {
  if (from.path !== to.path) { ScrollTrigger.refresh(); ScrollTrigger.update() }
})

 

  • Like 2
Link to comment
Share on other sites

Thanks for sharing your solution. Just FYI, you don't need to call ScrollTrigger.update() after calling ScrollTrigger.refresh() (it's redundant). 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×