I'm no React expert (maybe @Rodrigo will have some suggestions), but I noticed two minor things:
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
});
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
});