Jump to content
Search Community

ScrollTrigger and React component cycle cleanup

Lamonier test
Moderator Tag

Recommended Posts

Hey, everyone! I hope you're all having a great 2023 so far.

 

I've been having a blast experimenting with React in the past few months, but React has been reluctant to join the fun. More specifically the way it builds the page under the hood. Specially when you throw into the mix the strict mode, that runs everything twice.

 

GSAP's docs on pairing it with React are absolutely awesome and helped immensely. However, some edge cases showed up and were tricky to solve. The latest one was triggers showing up in the wrong place, but would immediately snap to the correct position if the viewport was resized. Disclaimer: I was not using GSAP context 🙈.

 

After doing some heavy testing I found that, although I was cleaning up inside the useLayoutEffect hook, the trigger wasn't being properly removed.

 

useLayoutEffect(() => {
  const myTl = gsap.timeline({
    scrollTrigger: {
      id: "trigger-id",
      // ...
  }
  
  // fancy effect
  
  return () => {
    ScrollTrigger.getById("trigger-id").kill();
    myTl.kill();
  }
  }, []);

 

Cleanup done. I killed the trigger, then the timeline. (I have a question, though. Killing the timeline also kills the trigger, right?)

Still, the error was there. Took me a while to figure out that the problem was happening with the trigger itself. Then I added:

 

useLayoutEffect(() => {
  if (ScrollTrigger.getById("trigger-id")) {
    ScrollTrigger.getById("trigger-id").kill();
  }
  
  // create timeline and its trigger
  // animation
  // cleanup (same as above)
}, []);

 

In the first render, if you try to .kill() the trigger you will get an error because you would be calling a method on something that doesn't exist.

To bypass that I added an if that checks if the trigger exists or not. That solved the problem like magic. Everything works fine now.

 

Since creating a trigger is something common when building a larger page, I created a helper function to shorten that.

 

// import not from the library itself,
// but from the file where you registered plugins
// (as described in the docs)
import { gsap, ScrollTrigger } from "./utils/gsapRegs";

const cleanTrigger = (config) => {
  if (ScrollTrigger.getById(config.scrollTrigger.id)) {
    ScrollTrigger.getById(config.scrollTrigger.id).kill();
  }
  
  const tl = gsap.timeline(config);
  return tl;
}

export default cleanTrigger;

 

So with that function, whenever you need to create a timeline that will work with ScrollTrigger, you call the cleanTrigger function and pass to it the object that you would pass to gsap.timeline(). The function first extracts the trigger id from the payload uses it to kill the trigger, if it exists. You get back a timeline with a "safe" trigger that you can use normally. Be careful, though, you still need to .kill() the timeline in the return function.

 

So...

 

// instead of
const myTl = gsap.timeline({
  scrollTrigger: {
    // ...
  },
})

// use
const mySafeTl = cleanTrigger({
  scrollTrigger: {
    /...
  },
})

 

That is the solution I came up with and any and all feedback is greatly appreciated. It is possible, if not probable, that I went through all this trouble because I missed something in the docs (or forums), but it's what ended up helping me.

 

I have one question: do I have to kill the trigger as well or just the timeline? Also, is there a better solution for this issue?

 

That's pretty much it. I hope that this helps someone that's going through the same problem either by being a solution or by being an opportunity to highlight this to distracted people like me.

 

Thanks in advance! 😃

Link to comment
Share on other sites

Hi,

 

48 minutes ago, Lamonier said:

(I have a question, though. Killing the timeline also kills the trigger, right?)

Depends if you create the ScrollTrigger in the configuration of the Timeline or if you create it with scrollTrigger.create(). In the first case, it does, the ScrollTrigger instance is gone. In the second case: nope the ScrollTrigger instance is still there.

 

51 minutes ago, Lamonier said:

I have one question: do I have to kill the trigger as well or just the timeline? Also, is there a better solution for this issue?

I think the response above should address this as well.

 

Just out of curiosity, did you tried your approach using GSAP Context. It should remove the need for all this, as far as I know. I'd like to see, if you have the time of course, a minimal demo of this in order to check it with GSAP Context and see how it works.

 

Here is an example that uses route transitions and as you can see everything works really good with GSAP Context:

https://stackblitz.com/edit/react-6rzfpp

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

I am terribly sorry for taking so long to reply, going through a rough patch.

But I did it! And could not replicate my problem!! 😅

 

See the Pen YzOpZGM by lrlamonier (@lrlamonier) on CodePen

 

I set up React and turned strict mode on. The code reflects exactly the setup I have. Each section is a component and the trigger structure is the same.

 

I'll have to do some refactoring on my components, so I'll take a moment to read the docs thoroughly and start using context.

  • Like 2
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.
×
×
  • Create New...