NickWoodward Posted April 16 Share Posted April 16 Similarly to the thread here I'm having problems typing my contextSafe function, but instead of getting “Type 'Function' is not assignable to type 'MouseEventHandler<HTMLDivElement>' I'm getting: Cannot invoke an object which is possibly 'undefined'.ts(2722) 'contextSafe' is possibly 'undefined'.ts(18048) const ref = useRef<HTMLDivElement>(null); useGSAP((context, contextSafe) => { const lines = gsap.utils.toArray<HTMLElement>(".line"); const burgerTl = gsap .timeline({ defaults: {duration:.4}, paused: true, }) .to(lines[0], { scaleX: 0 }) .to(lines[2], { scaleX: 0 }, '<.2') .to(lines[1], { rotate:135 }, '<.2') .set(lines[3], { display:'block', rotate:135}, '<.35') .to(lines[3], { rotate:45 }, '<'); const contextSafeClick = contextSafe(() => { // This call is the prob gsap.to(ref.current, {rotation: 180}); }); // ref.current.addEventListener("click", contextSafeClick); }); I can obviously make a guard condition (if(!contextSafe) return), but I thought it might be worth understanding why this is happening as I've not seen anyone else have to do this! Link to comment Share on other sites More sharing options...
Cassie Posted April 16 Share Posted April 16 Heya! Is this replicable on stackblitz at all? It would be good to see it in context, nothing wrong with the code that I can see. Link to comment Share on other sites More sharing options...
NickWoodward Posted April 16 Author Share Posted April 16 1 minute ago, Cassie said: Heya! Is this replicable on stackblitz at all? It would be good to see it in context, nothing wrong with the code that I can see. Yup I'll get on it now! Just thought it might be easy/obvious enough to not warrant one Link to comment Share on other sites More sharing options...
Rodrigo Posted April 16 Share Posted April 16 This is about type casting as explained in this thread Hopefully this helps Happy Tweening!, Link to comment Share on other sites More sharing options...
NickWoodward Posted April 16 Author Share Posted April 16 2 hours ago, Rodrigo said: This is about type casting as explained in this thread Hopefully this helps Happy Tweening!, Hi Rodrigo Yeah I saw that thanks (and I think the original thread linked above resulted in that recommendation by you too?) but I couldn't get it to work. It still seemed to complain that the safeContext function could be undefined. I probably did it wrong! See the Navigation component (line 15 and 27 of Navigation): https://stackblitz.com/edit/github-p21ql1?file=src%2Fcomponents%2FHeader.tsx,src%2Fpages%2Findex.astro,src%2Fcomponents%2FMobileBurger.tsx,src%2Fcomponents%2FNavigation.tsx,src%2Fcomponents%2FLogo.tsx Oh, and @Cassie here's the demo. Relevant bits in the Navigation component. Sorry it took so long! Link to comment Share on other sites More sharing options...
Rodrigo Posted April 16 Share Posted April 16 Hi, Why are you attaching an event handler like this in React? ref?.current?.addEventListener('click', contextSafeClick); That is most definitely not the way to do this, use onClick on the element and use contextSafe to wrap that event handler outside the scope of the useGSAP hook, like this: const { contextSafe } = useGSAP(() => {}); const clickHandler = contextSafe(() => { gsap.timeline(); // ... }) as React.MouseEventHandler; Then in your JSX <div {...props} className={classes} ref={ref} onClick={clickHandler}> <div className="line w-full h-[2px] bg-slate-400"></div> <div className="line w-full h-[2px] bg-slate-400"></div> <div className="line w-full h-[2px] bg-slate-400"></div> <div className="line-wrapper absolute top-0 left-0 flex items-center h-full w-full"> <div className="line hidden w-full h-[2px] bg-slate-400"></div> </div> </div> Using your current approach the event handler is being added twice as well since you have to remove the event handler manually, that is why is better to use the attributes React offers to do this: https://react.dev/learn/responding-to-events https://react.dev/reference/react-dom/components/common#react-event-object Hopefully this helps. Happy Tweening! Link to comment Share on other sites More sharing options...
NickWoodward Posted April 16 Author Share Posted April 16 39 minutes ago, Rodrigo said: Hi, Why are you attaching an event handler like this in React? ref?.current?.addEventListener('click', contextSafeClick); That is most definitely not the way to do this, use onClick on the element and use contextSafe to wrap that event handler outside the scope of the useGSAP hook, like this: const { contextSafe } = useGSAP(() => {}); const clickHandler = contextSafe(() => { gsap.timeline(); // ... }) as React.MouseEventHandler; Then in your JSX <div {...props} className={classes} ref={ref} onClick={clickHandler}> <div className="line w-full h-[2px] bg-slate-400"></div> <div className="line w-full h-[2px] bg-slate-400"></div> <div className="line w-full h-[2px] bg-slate-400"></div> <div className="line-wrapper absolute top-0 left-0 flex items-center h-full w-full"> <div className="line hidden w-full h-[2px] bg-slate-400"></div> </div> </div> Using your current approach the event handler is being added twice as well since you have to remove the event handler manually, that is why is better to use the attributes React offers to do this: https://react.dev/learn/responding-to-events https://react.dev/reference/react-dom/components/common#react-event-object Hopefully this helps. Happy Tweening! Thanks @Rodrigo - yeah normally I would've done, but I was following the docs I think? Isn't this the same as the example here: https://gsap.com/resources/React/ *edit: oh wait, mb, I was following the manual adding of listeners and forgot the cleanup fn being returned. i'll do it the other way const container = useRef(); const badRef = useRef(); const goodRef = useRef(); useGSAP((context, contextSafe) => { // ✅ safe, created during execution gsap.to(goodRef.current, {x: 100}); // ❌ DANGER! This animation is created in an event handler that executes AFTER useGSAP() executes. It's not added to the context so it won't get cleaned up (reverted). The event listener isn't removed in cleanup function below either, so it persists between component renders (bad). badRef.current.addEventListener("click", () => { gsap.to(badRef.current, {y: 100}); }); // ✅ safe, wrapped in contextSafe() function const onClickGood = contextSafe(() => { gsap.to(goodRef.current, {rotation: 180}); }); goodRef.current.addEventListener("click", onClickGood); // 👍 we remove the event listener in the cleanup function below. return () => { // <-- cleanup goodRef.current.removeEventListener("click", onClickGood); }; }, {scope: container}); return ( <div ref={container}> <button ref={badRef}></button> <button ref={goodRef}></button> </div> ); so this way instead const container = useRef(); const { contextSafe } = useGSAP({scope: container}); // we can pass in a config object as the 1st parameter to make scoping simple // ✅ wrapped in contextSafe() - animation will be cleaned up correctly // selector text is scoped properly to the container. const onClickGood = contextSafe(() => { gsap.to(".good", {rotation: 180}); }); return ( <div ref={container}> <button onClick={onClickGood} className="good"></button> </div> ); so useGSAP here takes an object instead of a function but returns a function that fulfils the same role? (allowing tls and animations to be added safely to the context?) Link to comment Share on other sites More sharing options...
Rodrigo Posted April 16 Share Posted April 16 Hi, There is nothing wrong with adding event listeners by hand, the code in the docs is good as long as you remove the event listener. I'm just saying that is better to just use the React way for this, nothing more. There will be times when you'll have to manually add an event listener, but most of the time using the approach React provides is the best choice. 34 minutes ago, NickWoodward said: so useGSAP here takes an object instead of a function but returns a function that fulfils the same role? (allowing tls and animations to be added safely to the context?) Yep, keep in mind that useGSAP uses GSAP Context under the hood: https://gsap.com/docs/v3/GSAP/gsap.context() So basically is a cleaner and simpler way to use the context add method: https://github.com/greensock/react/blob/main/src/index.js#L34 Happy Tweening! Link to comment Share on other sites More sharing options...
NickWoodward Posted April 16 Author Share Posted April 16 ok great, i'll have a read through that, thanks 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