Jump to content
Search Community

GSAP React State problems

fernandocomet test
Moderator Tag

Recommended Posts

Hi there!

 

I am making a portfolio site. I am building it in React and using GSAP.

I have found the following problem: Say my site has four sections:

Home / Text / Works / About

 

And I will use GSAP on section 2 "Text", so if I navigate to this section it is ok, but if you go to another section and go back to "Text" I have this warning:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

Also performance fails (as you see the text go changing but the order is broken)

 

I don´t know if it is asking too much, but maybe you can give me some guidance of what is really happening

 

I have seen documentation of GSAP and React and believe it is related to State management and components.  Not sure if I have to use React Transition Group in my component "Text.js" or how can I overtake this warning

 

Here you can see the error

Any help is welcome

 

See the Pen by s (@s) on CodePen

Link to comment
Share on other sites

Hi,

 

The main issue is that your GSAP instance keeps running as you go to another section. You need to stop all GSAP instances when a component is unmounted, your GSAP instance is still running and that calls the generateText method, which calls the changeText method, which updates the state of the component. Also in the console you can see that GSAP is complaining about not finding the message DOM element, that  is why we recommend using refs to get the dom element inside the render() method.

 

You should use componentWillUnmount in order to kill all GSAP instances, which will be created again when the component is mounted again by the router.

 

Finally I don't see the real upside to have both the colors and messages as static properties of the class. Since every JS file is it's own closure you can add them as constants outside the class and since the class and the constants reside in the same closure, they are accessible inside the class. Just an advice in order to keep the code inside the class to a bare minimum.

 

Happy Tweening!!!

  • Like 5
Link to comment
Share on other sites

I suggested to remove the colors and messages arrays from the class because I don't see the benefit of having them there, but keep the reference to the timeline inside the component class, because when the component is unmounted and then mounted again the reference could be lost.

 

This seems to be working fine for me:

constructor(props){
    super(props);
    this.state = {
      data:[],
      works:[],
      counter:0
    }
    this.initialSet = this.initialSet.bind(this);
    this.generateText = this.generateText.bind(this);
    this.changeText = this.changeText.bind(this);
    this.tl = gsap.timeline({ repeat: -1 });
  }

async componentDidMount(){
  const response = await fetch(`https://raw.githubusercontent.com/fernandocomet/website/master/fernandocomet/src/data/portfolio.json`);
  const json = await response.json();
  this.setState({ 
      data: json,
      counter: 0 
    });
  this.initialSet();
}

componentWillUnmount(){
    this.tl.kill();
}

initialSet(){
    
    let worksArr = [];
    // for (let i = 0; i < this.state.data.length ; i++){
    for (let i = 0; i < this.props.messages.length ; i++){
    //   worksArr.push(this.state.data[i].title)
      worksArr.push(this.props.messages[i].title)
    }
    this.setState({
      works: worksArr
    })
    
    //Here we go
    //var tl = gsap.timeline({ repeat: -1 });
    this.props.colors.forEach((color) => {
    this.tl.to("#message", { xPercent: -50, left: "50%", duration: 1, delay: 1 })
        .to("#bg", { backgroundColor: color.dark, duration: 1, delay: 3 })
        .to("#message", { color: color.light, duration: 2, delay: 0 })
        .add(this.generateText);
    });
}

generateText() {
    gsap.to("#message", { text: this.changeText(this.props.messages) });
}

changeText(arr) {
    console.log(this.state.counter);
    let counterChange = this.state.counter + 1;
    if (counterChange === 24) {
        counterChange = 0;
    }
    this.setState({ counter: counterChange })
    return this.props.messages[counterChange].title;
}

 

5 hours ago, fernandocomet said:

"Cannot remove node 35 because no matching node was found in the Store."

I'm unable to replicate this error though...

 

Happy Tweening!!!

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

  • 1 year later...

Hi everyone, 

 

Sorry to revive this not so old topic, I have a similar problem but with React hooks,

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I'm pretty new to React and I'm not sure what I can do to clean the gsap process.

 

I'm using gsap in two useEffects to drive simultaneously a countdown (in seconds going down) and a loading bar (in percent going up)

 

I tried the following :

	///// COUNTDOWN /////
    const [countdown, setCountdown] = useState(props.seconds);
    const proxyCount = useRef({ countdown }); 

    useEffect(() => {
        gsap.to(proxyCount.current, {
          countdown: 0,
          duration: props.seconds,
          ease: "linear",
          overwrite: "auto",
          onUpdate: () => {
            setCountdown(proxyCount.current.countdown);             
          }
        });

        return function cleanup() {
          gsap.killTweensOf(proxyCount, "countdown");
        };

      }, [setCountdown]);
    
   
///// PERCENT /////
    const [percent, setPercent] = useState(0);
    const proxy = useRef({ percent });
  
    useEffect(() => {
      gsap.to(proxy.current, {
        percent: 100,
        duration: props.seconds,
        ease: "linear",
        overwrite: "auto",
        onUpdate: () => {
          setPercent(proxy.current.percent);
        }
      });

      return function cleanup() {
        gsap.killTweensOf(proxy, "percent");
      };

    }, [setPercent]);

I thought that would be it but gsap.killTweensOf, either with or without the 2nd argument ( "countdown" / "percent" ) doesn't seem to fix it.

 

I'm not using timelines yet so I wondered if anyone has an alternative to the t1.kill() here.

 

Thanks in advance !

 

Julien

 

 

Link to comment
Share on other sites

Ok typical me, I think I found the solution 3 min after posting — always helps the brain to "explain"... 😅

 

So I'm setting let names tweenCount and tweenPercent to store the gsap.to instances,

and then simply tweenCount.kill() / tweenPercent.kill() inside the cleanup() functions

 

Now what I don't understand is why gsap.killTweensOf wouldn't work, while kill does. I'm really interested if something has an explanation for this !

Thanks !

  • Like 1
Link to comment
Share on other sites

@OSUblake thank you, that makes a little more sense.

I realize this came from the solution you gave me here, I'm really not serious student 😂

To be honest I still understand very little about .current, I need to study it.

If you happen to know some "for dummies" intro to it, I'm interested !

 

Also, in that case, would there be any reason to choose either kill() or killTweensOf()?

Or do they behave roughly the same (without extra variable for killTweensOf I guess)

.killTweensOf
Link to comment
Share on other sites

17 minutes ago, JuVince said:

To be honest I still understand very little about .current, I need to study it.

If you happen to know some "for dummies" intro to it, I'm interested !

 

Yes, it can be very confusing, and I'm not aware of any good tutorials for it. Just something you have to get used to. 

 

19 minutes ago, JuVince said:

Also, in that case, would there be any reason to choose either kill() or killTweensOf()?

 

They're pretty much the same. killTweensOf is useful if you want to kill more than 1, or just a certain property.

 

useEffect(() => {
  
  const animation1 = gsap.to(foo.current, { x: 100 });
  const animation2 = gsap.to(foo.current, { y: 100 });
  
  return () => {
    animation1.kill();
    animation2.kill();
  };
}, []);

// vs
useEffect(() => {
  
  gsap.to(foo.current, { x: 100 });
  gsap.to(foo.current, { y: 100 });
  
  return () => gsap.killTweensOf(foo.current);
}, []);

 

  • Like 3
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...