Jump to content
Search Community

Typing ContextSafe

NickWoodward test
Moderator Tag

Recommended Posts

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

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

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

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

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

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

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