Share Posted May 22, 2020 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 More sharing options...
Share Posted May 22, 2020 @OSUblake, @Rodrigo, and @elegantseagulls use React the most around here. Hopefully they'll pop in and can help 1 Link to comment Share on other sites More sharing options...
Share Posted May 22, 2020 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!!! 5 Link to comment Share on other sites More sharing options...
Author Share Posted May 24, 2020 Ok I´m trying to perform an example with what you say. I´ve defined out the timeline and have added ComponentWillMount, don´t have previous error but a new one: "Cannot remove node 35 because no matching node was found in the Store." https://codesandbox.io/s/gsap-react-state-problems-swnuv?file=/src/Text.js Link to comment Share on other sites More sharing options...
Share Posted May 24, 2020 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!!! 4 1 Link to comment Share on other sites More sharing options...
Author Share Posted May 25, 2020 Thank you very much Rodrigo for you help and assistance. I see it clear now. This colors and sentences props I will change them to dynamic data. Thank you again! Link to comment Share on other sites More sharing options...
Share Posted June 23, 2021 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 More sharing options...
Share Posted June 23, 2021 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 ! 1 Link to comment Share on other sites More sharing options...
Share Posted June 23, 2021 47 minutes ago, JuVince said: Now what I don't understand is why gsap.killTweensOf wouldn't work GSAP isn't animating proxyCount. It's animating proxyCount.current. Perhaps you meant this. gsap.killTweensOf(proxyCount.current); 1 Link to comment Share on other sites More sharing options...
Share Posted June 23, 2021 1 hour ago, JuVince said: Sorry to revive this not so old topic, I have a similar problem but with React hooks, No problem, but it's usually best to start a new topic. Link to comment Share on other sites More sharing options...
Share Posted June 23, 2021 @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 More sharing options...
Share Posted June 23, 2021 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); }, []); 3 Link to comment Share on other sites More sharing options...
Share Posted June 23, 2021 That's very clear thank you so much ! I converted everything to use killTweensOf, it just seems more readable and economical. I found this reference for useRef, will take a closer look 2 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now