Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
TomSES

Update React State with onComplete and repeat

Recommended Posts

I'm sure this is something I'm doing wrong but I can't seem to accurately update state from an onComplete event. 

 

The counter successfully updates once but then seems stuck on '1'. The example below is very simple to show the issue.

 

https://stackblitz.com/edit/react-l6a9q7

 

Any help would be really helpful


Thanks so much!

Share this post


Link to post
Share on other sites

Hey Tom. You just need to pass in the count to the useEffect:
 

useEffect(() => {
  tl.current.to(test, 2, { opacity: 0, onComplete: update });
}, [count]);

 

Share this post


Link to post
Share on other sites

That's because it will create a new tween every time that the count state updates. Sorry, I don't really use React and this is 100% a React question so I'm probably not the best equipped person to do it :) 

 

If this is all you're doing then you could just use a tween with overwrite: true https://stackblitz.com/edit/react-nempcw

With that being said, @OSUblake, @Rodrigo, or @elegantseagulls will probably come by and correct my ignorance.

 

Also FYI there was a recent article made about hooks + GSAP by @Ihatetomatoes that might be useful: https://ihatetomatoes.net/react-and-greensock-tutorial-for-beginners/

Share this post


Link to post
Share on other sites

Well you have it working so it's a huge step up from my efforts! I guess if I can store the whole tl in a ref, it won't create a new each time. You've definitely pointed me in the right direction so thanks!

Share this post


Link to post
Share on other sites

Hey Tom, it's Petr here from Ihatetomatoes.net, was the article that @ZachSaucier pointed you to useful? Let me know if I should add anything to the guide. Cheers

Share this post


Link to post
Share on other sites

Hi Petr, I checked out the guide and it was really useful so thank you! Unfortunately, I could not see how to solve the specific issue I had other than how @ZachSaucier kindly showed me. Did you take a look at the first couple of examples to see the issue?

Share this post


Link to post
Share on other sites

Hm I am not 100% sure what effect you are trying to create but when I pass test.current to the tween I don't see any issue with updating the state in the onComplete.

 

https://stackblitz.com/edit/react-bowg4f

  • Like 1

Share this post


Link to post
Share on other sites

Again, I am not sure what effect you are trying to achieve but the link I posted would be my way how to update state after a tween is completed. Hope that helps.

Share this post


Link to post
Share on other sites

That's great, that is the one I have used which was the same method that Zach recommended. I think he was just looking for validation from someone with more React experience so thanks for providing that! :) i really appreciate how helpful everyone is on this forum.

Share this post


Link to post
Share on other sites

No problem. I have just changed the way the ref is used in the markup and passed text.current to the tween, that seems to be the right approach according to the official React useRef docs.

  • Like 2

Share this post


Link to post
Share on other sites

Hey guys,

 

I've been looking into this and I really don't like this part:

useEffect(() => {
  console.log(tl.current);
  tl.current.to(test.current, 2, { opacity: 0, onComplete: update });
}, [count]);

Basically this code tells GSAP to add a new instance to the end of the timeline each time the count value in the component's state is updated, which happens every time the timeline instance is completed. This means that the timeline gets bigger and bigger, which could lead to unwanted results and a memory leak. The instance should be added to the timeline only once in the initial render of the component, using an empty array in the useEffect() hook.

 

I'm trying to figure something out in order to post a solution with that particular code, but I've been interrupted several times by household situations :D. As soon as I have something concrete to post I'll get back to you.

 

Happy Tweening!!!

 

PS:

46 minutes ago, Ihatetomatoes said:

Let me know if I should add anything to the guide.

Yeah, I have two suggestions. One, don't use Hooks with GSAP. Two, use Vue or Svelte :D:D:D

  • Like 2

Share this post


Link to post
Share on other sites

Ok, so I don't know what to tell you besides don't use hooks with GSAP.

 

This is a version using hooks with the approach I mentioned before:

 

https://codesandbox.io/s/gsap-update-state-hooks-4nezn

 

It doesn't work.

 

This is basically the same code using a class component:

 

https://codesandbox.io/s/gsap-update-state-class-tdj1i

 

This does work.

 

I'll ask around in the reactiflux community to see if someone can shed some light into this.

 

Happy Tweening!!!

  • Like 3

Share this post


Link to post
Share on other sites

Ok, so the issue is stated here:

 

https://reactjs.org/docs/hooks-reference.html#functional-updates

 

Basically since the closure in the initial render can access only the initial state, so you need to explicitly pass the current state to the callback using the functional update approach:

const update = () => {
  setCount(prevCount => prevCount + 1);
};

So this sample now works as expected:

 

https://codesandbox.io/s/gsap-update-state-hooks-4nezn

 

And apparently I have to eat my words and you CAN use GSAP with the hooks API :D

giphy.gif

 

Happy Tweening!!!

  • Like 3

Share this post


Link to post
Share on other sites

What an unintuitive way of handling things...

  • Like 1

Share this post


Link to post
Share on other sites

Thanks Rodrigo for the code examples. I know most people here prefer the class approach for the more readable code, but I think it's useful to give people a clear way how to use GSAP tween and timeline with both class and hooks approach.

 

If you think there is something missing or not clear enough in this guide please let me know.

 

https://ihatetomatoes.net/react-and-greensock-tutorial-for-beginners/

 

I will create more guides for React + GSAP and class approach would definitely be one of them.

  • Like 2

Share this post


Link to post
Share on other sites
55 minutes ago, ZachSaucier said:

What an unintuitive way of handling things...

 

That's hooks! It calls the same function on every render/update, so values won't persist. Only the initial ones.

 

That's another reason why I don't recommend creating animations inside a useRef call. It will create a new animation and then throw it away on every update. 

 

export default function App() {
  
  // creates a new timeline on every update
  // keeps the original, and throws away the new one
  const tl = useRef(gsap.timeline({ paused: true, repeat: -1 }));

  useEffect(() => {
    tl.current
      .to(testEl.current, {
        duration: 1,
        opacity: 0,
        onComplete: update
      })
      .play();
  }, []);
}

 

Better approach.

 

export default function App() {
  
  const tl = useRef();

  useEffect(() => {
    
    tl.current = gsap.timeline({ repeat: -1 })
      .to(testEl.current, {
        duration: 1,
        opacity: 0,
        onComplete: update
      });
    
    return () => tl.current.kill(); // return function to kill on unmount
  }, []);
}

 

  • Like 5

Share this post


Link to post
Share on other sites
1 hour ago, Ihatetomatoes said:

Thanks Rodrigo for the code examples. I know most people here prefer the class approach for the more readable code, but I think it's useful to give people a clear way how to use GSAP tween and timeline with both class and hooks approach.

 

If you think there is something missing or not clear enough in this guide please let me know.

 

https://ihatetomatoes.net/react-and-greensock-tutorial-for-beginners/

 

I will create more guides for React + GSAP and class approach would definitely be one of them.

Hey Peter,

 

No problem mate!!! we are all here pushing in the same direction. Since you have a dedicated section for GSAP+React, it would be nice to have this kind of samples and as many case scenarios covered in order to refer users to your site.

 

Mostly the issue we (Blake and I) have is that the Hooks API ends up being confusing and somehow filled with this hurdles you have to navigate. @ZachSaucier already said it better than I can:

2 hours ago, ZachSaucier said:

What an unintuitive way of handling things...

 

Components are easier and their API makes more sense. Also that's why the more I work with Vue, the more I prefer it as an approach to build apps, because in most cases you have only one way to do things and that is a great feature for a library that helps you build apps. Also Vue allows to separate the HTML from the JS and finally in version 3 the composition API is going to be a great addition.

 

I'll check the tutorial on the weekend and get back to you.

 

Happy Tweening!!!

  • Like 2

Share this post


Link to post
Share on other sites
39 minutes ago, Rodrigo said:

Components are easier and their API makes more sense.

 

One of the arguments for hooks is that you don't have to use this. So instead of using this.someValue, you now have to use someValue.current. I don't see how that is an improvement. 🤷‍♂️

 

43 minutes ago, Rodrigo said:

Also Vue allows to separate the HTML from the JS and finally in version 3 the composition API is going to be a great addition

 

So simple.

 

<template>
  <button @click="inc">{{ count }}</button>
</template>

<script setup>
import { ref } from 'vue'

export const count = ref(0)
export const inc = () => count.value++
</script>

 

  • Like 3

Share this post


Link to post
Share on other sites

This thread has been really informative and makes me think we jumped ship from class components too soon. It was unusual behaviour like this which made us move from Angular to React in the first place.

 

Thanks, all for providing a few different solutions to this!

  • Like 1

Share this post


Link to post
Share on other sites
On 7/10/2020 at 10:45 PM, OSUblake said:

Better approach.

 


export default function App() {
  
  const tl = useRef();

  useEffect(() => {
    
    tl.current = gsap.timeline({ repeat: -1 })
      .to(testEl.current, {
        duration: 1,
        opacity: 0,
        onComplete: update
      });
    
    return () => tl.current.kill(); // return function to kill on unmount
  }, []);
}

 

Took the words out of my fingers here, @OSUblake. This is, more or less, the approach we use. For "on-load" animations we often use useCallback  instead of useEffect.

  • Like 2

Share this post


Link to post
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.

×