Hello everyone,
I have a component like this:
"use client";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { useEffect, useState, useRef } from "react";
import Hamburger from "hamburger-react";
import * as NavigationMenu from "@radix-ui/react-navigation-menu";
import LangSelector from "@/ui/molecules/lang-selector";
import { Link } from "@/lang/navigation";
import { LinkItem } from "@/types/nav";
gsap.registerPlugin(useGSAP);
function Header({ navItems }: { navItems: LinkItem[] }) {
const [isOpen, setOpen] = useState(false);
const [isMounted, setIsMounted] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
const mmRef = useRef<gsap.MatchMedia>(gsap.matchMedia());
const tlRef = useRef<gsap.core.Timeline | null>(null);
useGSAP(
() => {
mmRef.current.add("(width < 768px)", () => {
tlRef.current = gsap.timeline({
paused: true,
defaults: { ease: "power1.inOut", duration: 0.3 },
});
tlRef.current
.to(".nav", {
autoAlpha: 1,
})
.from(
"li",
{
x: -20,
autoAlpha: 0,
stagger: 0.1,
},
"<",
);
});
},
{
scope: containerRef,
},
);
useGSAP(
() => {
if (!tlRef.current) return;
isOpen ? tlRef.current.play() : tlRef.current.reverse();
},
{
dependencies: [isOpen],
},
);
// this is because rehydratation errors in the component
useEffect(() => {
setIsMounted(true);
}, []);
return (
<header className="header" ref={containerRef}>
<div className="wrapper">
<Link href="/" className="logo-wrapper">
<img className="logo" src="/images/logo-gray.webp" alt="Estudio Leda" />
</Link>
<div className="controls-wrapper">
<LangSelector />
<div className={`burger ${isOpen ? "--open" : ""}`}>
<Hamburger toggled={isOpen} toggle={setOpen} label={"open menu button"} />
</div>
</div>
<NavigationMenu.Root asChild>
<nav className="nav">
<NavigationMenu.List className="nav__list">
{navItems.map((item) => (
<NavigationMenu.Item key={item.label}>
<NavigationMenu.Link asChild>
<Link className="nav__link" href={item.href}>
{item.label}
</Link>
</NavigationMenu.Link>
</NavigationMenu.Item>
))}
</NavigationMenu.List>
<LangSelector />
</nav>
</NavigationMenu.Root>
</div>
</header>
);
}
export default Header;
The component works just fine. When I click the button and the isOpen state, it fires the useGSAP hook, and it plays or reverses the timeline according to the state.
Is there a better/clean way to do this? I don't like the idea of having 2 useGSAP calls, the first to define the animations, and the 2nd to play or reverse the timeline according to the state. I could use useEffect, but since useGSAP is a drop in replacement for useEffect I chose to go that way.
Its a good practice to store the Match Media and Time Line obejcts in refs?
Thanks!