Jump to content
Search Community

GSAP with React, useGSAP correct usage

Cristian Coppari test
Moderator Tag

Recommended Posts

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.

 

  1. 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.
  2. Its a good practice to store the Match Media and Time Line obejcts in refs?

Thanks!

Link to comment
Share on other sites

Hi,

 

I don't think you need to store the MatchMedia instance in a ref. Will you add to that instance later on in your code? If not there is no need IMHO.

 

7 minutes ago, Cristian Coppari said:

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.

Agreed, that looks cumbersome and verbose, but given the fact that you need that to play/reverse based on a state property there isn't a lot of other alternatives TBH. I would try to avoid using state to toggle the instance, but if you actually need that state then use it. Another thing you could try is see if the hamburger component has an onClick prop, so you can call a method that toggles the animation, like this demo:

https://stackblitz.com/edit/gsap-react-basic-f48716?file=src%2FApp.js

 

Hopefully this helps.

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