@gsap/react for using GSAP in React
GSAP itself is completely framework-agnostic and can be used in any JS framework without any special wrappers or dependencies. This hook solves a few React-specific friction points so that you can just focus on the fun stuff. 🤘🏻
useGSAP()
A drop-in replacement for useEffect()
or useLayoutEffect()
that automatically handles cleanup using gsap.context()
❌ OLD (without useGSAP() hook)
import { useEffect, useLayoutEffect, useRef } from "react";
import gsap from "gsap";
const useIsomorphicLayoutEffect = (typeof window !== "undefined") ? useLayoutEffect : useEffect;
const container = useRef();
useIsomorphicLayoutEffect(() => {
const ctx = gsap.context(() => {
}, container);
return () => ctx.revert();
}, []);
✅ NEW
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
const container = useRef();
useGSAP(() => {
}, { scope: container });
...or with a dependency Array and scope:
useGSAP(() => {
}, { dependencies: [endX], scope: container});
If you prefer the method signature of useEffect()
and you don't need to define a scope, this works too but the config
object syntax is preferred because it offers more flexibility and readability:
useGSAP(() => {
}, [endX]);
So you can use any of these method signatures:
useGSAP(func, config);
useGSAP(func);
useGSAP(func, dependencies);
const { context, contextSafe } = useGSAP(config);
If you define dependencies
, the GSAP-related objects (animations, ScrollTriggers, etc.) will only get reverted when the hook gets torn down but if you want them to get reverted every time the hook updates (when any dependency changes), you can set revertOnUpdate: true
in the config
object.
useGSAP(() => {
}, { dependencies: [endX], scope: container, revertOnUpdate: true });
Benefits
- Automatically handles cleanup using
gsap.context()
- Implements
useIsomorphicLayoutEffect()
technique, preferring React's useLayoutEffect()
but falling back to useEffect()
if window
isn't defined, making it safe to use in server-side rendering environments. - You may optionally define a
scope
for selector text, making it safer/easier to write code that doesn't require you to create a useRef()
for each and every element you want to animate. - Defaults to using an empty dependency Array in its simplest form, like
useGSAP(() => {...})
because so many developers forget to include that empty dependency Array on React's useLayoutEffect(() => {...}, [])
which resulted in the code being executed on every component render. - Exposes convenient references to the
context
instance and the contextSafe()
function as method parameters as well as object properties that get returned by the useGSAP()
hook, so it's easier to set up standard React event handlers.
Install
npm install @gsap/react
Using callbacks or event listeners? Use contextSafe()
and clean up!
A function is considered "context-safe" if it is properly scoped to a gsap.context()
so that any GSAP-related objects created while that function executes are recorded by that Context
and use its scope
for selector text. When that Context
gets reverted (like when the hook gets torn down or re-synchronizes), so do all of those GSAP-related objects. Cleanup is important in React and Context
makes it simple. Otherwise, you'd need to manually keep track of all your animations and revert()
them when necessary, like when the entire component gets unmounted/remounted. Context
does that work for you.
The main useGSAP(() => {...})
function is automatically context-safe of course. But if you're creating functions that get called AFTER the main useGSAP()
function executes (like click event handlers, something in a setTimeout()
, or anything delayed), you need a way to make those functions context-safe. Think of it like telling the Context
when to hit the "record" button for any GSAP-related objects.
Solution: wrap those functions in the provided contextSafe()
to associates them with the Context
. contextSafe()
accepts a function and returns a new context-safe version of that function.
There are two ways to access the contextSafe()
function:
1) Using the returned object property (for outside useGSAP()
function)
const container = useRef();
const { contextSafe } = useGSAP({scope: container});
const onClickBad = () => {
gsap.to(".bad", {y: 100});
};
const onClickGood = contextSafe(() => {
gsap.to(".good", {rotation: 180});
});
return (
<div ref={container}>
<button onClick={onClickBad} className="bad"></button>
<button onClick={onClickGood} className="good"></button>
</div>
);
2) Using the 2nd argument (for inside useGSAP()
function)
const container = useRef();
const badRef = useRef();
const goodRef = useRef();
useGSAP((context, contextSafe) => {
gsap.to(goodRef.current, {x: 100});
badRef.current.addEventListener("click", () => {
gsap.to(badRef.current, {y: 100});
});
const onClickGood = contextSafe(() => {
gsap.to(goodRef.current, {rotation: 180});
});
goodRef.current.addEventListener("click", onClickGood);
return () => {
goodRef.current.removeEventListener("click", onClickGood);
};
}, {scope: container});
return (
<div ref={container}>
<button ref={badRef}></button>
<button ref={goodRef}></button>
</div>
);
scope
for selector text
You can optionally define a scope
in the config
object as a React Ref and then any selector text in the useGSAP()
Context
will be scoped to that particular Ref, meaning it will be limited to finding descendants of that element. This can greatly simplify your code. No more creating a Ref for every element you want to animate! And you don't need to worry about selecting elements outside your component instance.
Example using Refs (tedious) 😩
const container = useRef();
const box1 = useRef();
const box2 = useRef();
const box3 = useRef();
useGSAP(() => {
gsap.from([box1, box2, box3], {opacity: 0, stagger: 0.1});
});
return (
<div ref={container}>
<div ref={box1} className="box"></div>
<div ref={box2} className="box"></div>
<div ref={box3} className="box"></div>
</div>
);
Example using scoped selector text (simple) 🙂
const container = useRef();
useGSAP(() => {
gsap.from(".box", {opacity: 0, stagger: 0.1});
}, { scope: container });
return (
<div ref={container}>
<div className="box"></div>
<div className="box"></div>
<div className="box"></div>
</div>
);
Demos and starter templates
https://stackblitz.com/@gsap-dev/collections/gsap-react-starters
Need help?
Ask in the friendly GSAP forums. Or share your knowledge and help someone else - it's a great way to sharpen your skills! Report any bugs there too (or file an issue here if you prefer).
License
GreenSock's standard "no charge" license can be viewed at https://gsap.com/standard-license. Club GSAP members are granted additional rights. See https://gsap.com/licensing/ for details. Why doesn't GSAP use an MIT (or similar) open source license, and why is that a good thing? This article explains it all: https://gsap.com/why-license/
Copyright (c) 2008-2024, GreenSock. All rights reserved.