Jump to content
Search Community

React state not persisting using ScrollTrigger OnStart

chuck77 test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

Hello,

 

First of all, I want to say that GSAP has been a fantastic product that has solved many problems for me.  I really enjoy using it for my projects!

 

Lately, I started to use scrollTrigger with React and I noticed a strange behaviour.

 

In my minimal codesandbox example, I designed it so that the scrollTrigger is only allow to slide the "door" div to the right during onEnter on the first time only.  I am using React state in "iterCount" to prevent the door div from triggering any subsequent time.

 

However, when I console.log the iterCount state, it appears that the scrollTrigger never received the updated iterCount after it was incremented by the onComplete event.  Meanwhile, the iteration count is correctly incrementing in the React App itself in the counter at the top of the page.  What is missing here?

 

https://codesandbox.a/s/gsap-updating-state-hooks-sliding-door-xucoo0

See the Pen by s (@s) on CodePen

Link to comment
Share on other sites

  • Solution

Hi @chuck77, welcome to the GreenSock forums and thanks for the kind words!

 

First you are using extremely old versions of both GSAP and React, any particular reason for that?

 

Also you should use GSAP Context when working in React apps and it's ecosystem (Next, Gatsby, etc):

https://greensock.com/docs/v3/GSAP/gsap.context()

 

I think you are overcomplicating things a bit. I can think of two simpler ways to do this without all this state hassle you're running into because of scoping and the way useState works.

 

Also you have an issue in the way you're handling the timeline instance, since you're adding new tweens at the end of it, everytime you run those methods:

const handleClick = () => {
  console.log(`handleClick ${iterCount}`);
  tl.current
    .to(doorRef.current, {
    duration: 1,
    // opacity: 0,
    x: "+=100"
  })
    .to(doorRef.current, {
    duration: 1,
    // opacity: 1,
    onComplete: update
  })
    .play();
};

const returnDoor = () => {
  tl.current
    .to(doorRef.current, {
    duration: 1,
    x: "0"
  })
    .play();
};

One option is to set the end point to one pixel after the start point and use the config once: true in the ScrollTrigger instance in order to run it once and then the ScrollTrigger instance gets removed automatically:

ScrollTrigger.create({
  trigger: doorRef.current,
  start: "top center",
  end: "+=1",
  once: true,
  onEnter: enterHandler,
  markers: true,
});

The second option is not use state and use a ref to store a boolean and update it. I added this second option because you have a button that returns the element to it's original state, so I assume that you want to trigger the animation in the onEnter callback if the element is on it's original position. Also I think is far better to just create the timeline once and play/reverse it depending on the method you run:

const doorRef = useRef(null);
const doorOpened = useRef(false);
const tl = useRef();

const returnDoor = () => {
  doorOpened.current = false;
  tl.current.pause();
  gsap.to(tl.current, { progress: 0, duration: 1 });
};

const enterHandler = () => {
  if (doorOpened.current) return;
  doorOpened.current = true;
  tl.current.restart();
};

useEffect(() => {
  const ctx = gsap.context(() => {
    tl.current = gsap
      .timeline({ paused: true, repeat: 0 })
      .to(doorRef.current, {
        duration: 1,
        // opacity: 0,
        x: "+=100",
      })
      .to(doorRef.current, {
        duration: 1,
        // opacity: 1,
      });
    ScrollTrigger.create({
      trigger: doorRef.current,
      start: "top center",
      end: "+=1",
      once: true,
      onEnter: enterHandler,
      markers: true,
    });
  });

  return () => ctx.revert();
}, []);

Here is a live example:

https://codesandbox.io/s/gsap-updating-state-hooks-sliding-door-forked-6yymc5

 

As you can see the example uses a single timeline and play/reverses it based on the event handler.

 

Hopefully this clear things up.

 

Happy Tweening!

  • Like 2
Link to comment
Share on other sites

Thank you @Rodrigo! I got it to work properly with gsap context.

 

However, what I don't fully understand is how this fixed the react state so that it persists when using it on scrollTrigger events like onComplete, onEnter, etc.  Is it because my previous method did not access React context, making the state not actually recorded?

Link to comment
Share on other sites

@chuck77 I haven't looked closely since the link in your OP is going to an error page for me, but I'll give my best explanation.

When you use GSAP + React, the biggest issue to overcome is the fact that GSAP is not Reactive, nor does it "live" in react. There are some ways you can sort of get it to interact with state documented in the Advanced GSAP + React guide, but generally I would avoid it unless you know exactly what you are doing.

If you are instancing a scrollTrigger that consumes a piece of state, that means the function/tween that uses the scrollTrigger must be re-run to create itself with the new value in mind.

Rodrigo's example isn't using React Context, it is using gsap.context(). He isn't using React State either. Instead he creates the ScrollTrigger and puts it in a React Ref so that it persists across re-renders. Nothing needs to be stored in React State because all that information is maintained withing ScrollTrigger itself.

If you are new to GSAP, I highly, HIGHLY recommend getting some experience with in in Vanilla JS first. React is a cool tool, but it only causes problems when it comes to animations. Figure out the vanilla implementation first and try implementing it from there.

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...