Jump to content
Search Community

Best Practices for Button-Triggered GSAP Animations in React Before Re-rendering

FilipSzemraj test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hi everyone,

I am just starting with GSAP and React and want to adopt best practices from the beginning. I would appreciate your insights on my approach.

 

My question concerns triggering an animation before a component re-renders, similar to the discussion in this topic. However, my scenario involves initiating the animation with a button's onClick event.

 

Currently, I am using forwardRef and useImperativeHandle in React, which allows me to activate a function from a button in a separate component. This function triggers a fade-out animation in the component where the animation is defined. In the animation's onComplete callback, I modify a variable that is part of the prop in the main component, causing it to re-render.

 

Is there a better way to achieve this effect? Also, if you have any comments or suggestions after reviewing my code, I would be extremely grateful to hear them.

 

Here is link to the  stackblitz with the minimal demohttps://stackblitz.com/edit/stackblitz-starters-v1vstg?file=src%2Fcomponents%2Fplaceholder.js

 

PS. I forgot mention that I want this effect to works when the buttons are in different component than the animated content. I already add this functionality to the demo.

Edited by FilipSzemraj
Add functionality
Link to comment
Share on other sites

  • Solution

I'm no React expert (maybe @Rodrigo will have some suggestions), but I noticed two minor things: 

 

  1. You're putting a stagger on a tween that's only has one target, so it's pointless: 
    delay: 0.7 + index * 0.1,
    stagger: 0.1, // pointless. There's only one target

    You could simplify that whole thing: 

    // BEFORE (long)
    iconsRef.current.forEach((icon, index) => {
      const isInProject = projectTechnologies[activeProject]?.includes(index);
      gsap.fromTo(
        icon,
        {
          opacity: 0,
          scale: 0.8,
        },
        {
          opacity: 1,
          scale: isInProject ? 1.05 : 0.8,
          delay: 0.7 + index * 0.1,
          stagger: 0.1,
        }
      );
    });
    
    // AFTER (short)
    gsap.fromTo(iconsRef.current, {
      opacity: 0,
      scale: 0.8,
    }, {
      opacity: 1,
      scale: i => projectTechnologies[activeProject]?.includes(i) ? 1.05 : 0.8,
      delay: 0.7,
      stagger: 0.1
    });

     

  2. I don't understand why you're animating to the CURRENT scale/filter values in this tween (what's the point?). Plus you could simplify it all into a very simple tween: 
    // BEFORE
    iconsRef.current.forEach((icon, index) => {
      const currentScale = gsap.getProperty(icon, 'scale');
      const currentFilter = gsap.getProperty(icon, 'filter');
    
      gsap.to(icon, {
        scale: currentScale,
        filter: currentFilter,
        opacity: 0,
        delay: 0,
        onComplete: () => {
          if (index === iconsRef.current.length - 1) {
            handleFadeIn();
          }
        },
      });
    });
    
    // AFTER
    gsap.to(iconsRef.current, {
      opacity: 0,
      onComplete: handleFadeIn
    });

 

  • Thanks 1
Link to comment
Share on other sites

Posted (edited)

Thank you very much. 

 

I must have left this error from an earlier version; if it concerns the second point, that's a similar story. Before I managed to run the fade-out animation without using a useState variable, I was attempting to hide the effect of different sizes caused by re-rendering. However, with the current understanding, it seems to be nonsensical.

 

I think that if it works, and indeed it does, it doesn't need to be improved. However, maybe Mr. @Rodrigo has something to say about the way the button's onClick function is passed? Thank you in advance.

 

I think I found the solution here: https://gsap.com/resources/react-advanced/

 

Edited by FilipSzemraj
found a solution
  • Like 1
Link to comment
Share on other sites

Hi,

 

Your demo currently is not working, but I did noticed something that caught my attention:

function CardComponent() {
  const IconWrapper = forwardRef(({ children, activeProject }, ref) => {});
  
  const TechnologyBody = forwardRef(({ activeProject }, ref) => {});
}

Any particular reason to create other components in the callback of another component? This looks real odd to me TBH, because your CardComponent has quite some props, so if any of those props is updated all the component will be re-rendered and those functions will be created again and again, seems odd and a potential source of issues. Personally I wouldn't do it like this and follow this pattern:

https://react.dev/reference/react/forwardRef#exposing-an-imperative-handle-instead-of-a-dom-node

 

Finally forwarding refs is a way to overcome a shortcoming in React that are emitters/listeners, something any self respecting framework should have, like Vue: 

https://vuejs.org/guide/components/events.html#emitting-and-listening-to-events

 

Svelte:

https://svelte.dev/repl/ba7f569af4a44553b201a9efd8dc6ec2?version=4.2.12

 

So certainly forwardRef adds a layer of complexity to all this, but indeed your idea is the best way I can think of in terms of calling a child method from a parent and then using he onComplete to update some state. Another option would be to use a React Context provider and run all the logic there as well. Finally yet another alternative could be a custom hook, but at the end just use the one that works better in your particular case.

 

Happy Tweening!

  • Thanks 1
Link to comment
Share on other sites

I've updated my code and created a fork using a context approach. Initially, I thought that when using a variable passed as props, the entire component would re-render. However, now I'm a bit confused because, with the context, a simple useEffect is providing a fade-out function. I was under the impression that I might need to use forwardRef along with context, but it turns out my implementation works fine without it, suggesting my current approach could be correct.

 

Could you, @Rodrigo, please take one more look at my revised code and let me know if there are any issues with the structure? I believe I've fixed everything, and I'd like to ensure the context approach is implemented correctly.

 

I'm truly grateful for your assistance. Currently, I'm working on my portfolio and planning to start a blog soon. My goal is to share the knowledge I've gained with others who might find my posts helpful. In doing so, I hope to extend the reach of your help to a broader audience.

 

stackblitz links:

without context

with context

 

Link to comment
Share on other sites

12 hours ago, FilipSzemraj said:

However, now I'm a bit confused because, with the context, a simple useEffect is providing a fade-out function. I was under the impression that I might need to use forwardRef along with context, but it turns out my implementation works fine without it, suggesting my current approach could be correct.

That's exactly what React Context does, it removes the need for forwardRef and useImperativeHandler by using an approach that relies on state management rather than direct event handlers.

 

I don't have time to go through your code with a lot of detail but both seem to work as expected. The most important part here is if both approaches work pick the one that makes your life easier in terms of debugging and code maintenance, that's it. At least that's how I make my decisions in these subjects. Sometimes the fancy code that gets everything done in 20 lines can be a pain to decipher and you might find yourself looking at that code in six months and saying: "What the heck was I doing here??!!" So it could take some time to wrap your mind around it and be able to solve any issues or make improvements. Most times the simpler solution that might seem ugly and it doesn't cause any performance issue is the best.

 

Just my two cents.

Happy Tweening!

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