Shots in this scene
GSAP in React with useGSAP
In one line
useGSAP from @gsap/react is useEffect for GSAP: it runs your animation code in a scoped context after render and reverts it when the component updates or leaves, which also makes React's StrictMode double run harmless.
Think of it like this
A hotel room. Each guest (a component) gets a room with their own things; at check-out the cleaners reset it. If the cleaners skip a room, the next guest finds the last one's music still playing. useGSAP is the cleaning schedule for GSAP animations in React components.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- useGSAP
- A React hook from
@gsap/reactthat runs GSAP code in a scoped, revertible context. - scope
- A ref to the element that string selectors inside the hook are limited to.
- contextSafe
- A wrapper for event handlers so the tweens they start belong to the hook's context.
- revertOnUpdate
- An option that reverts the previous run when the dependencies change.
Step by step
01Count the runs in StrictMode
In "useGSAP in a component" the console prints effect run 1 and effect run 2. React ran the effect, reverted it, and ran it again. Three dots are found each time, because ".dot" is scoped to the component, and the second run starts from clean dots.
02Open the panel with a context-safe handler
Press Open the panel: it grows to 40 px over 0.4 s, and its label changes to Close. The handler is wrapped in contextSafe, so this tween is reverted if the component leaves.
03Compare the two hooks
In "useEffect against useGSAP" two lanes loop a dot. The console, 300 ms after mount, says there are 2 tweens on the useEffect dot and 1 on the useGSAP dot. The first one has two tweens fighting over x forever.
04Take StrictMode off
Without <StrictMode> the useEffect dot has 1 tween, not 2: the missing cleanup was always there.
Try it yourself
- 1
Take StrictMode off
In "useEffect against useGSAP" replace
<StrictMode><App /></StrictMode>with<App />. The first line now readstweens on the useEffect dot: 1: the leak only showed because StrictMode ran the effect twice. A component that remounts in production (a route change, a toggled panel) would leak the same way. - 2
Read the panel's height mid-move
In "useGSAP in a component" pause, press Open the panel and step to 200 ms: the panel is 30 px tall (75% of the way, by
power1.out), and 40 px at the end. It is aheighttween (a layout property), fine for a small panel; for a big one use Phase 10's techniques.
Code & diagrams
A scoped hook, dependencies with revertOnUpdate, and a context-safe click handler. The console shows StrictMode's double run.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
effect run 1 | dots found: 3
effect run 2 | dots found: 3The same loop twice, in StrictMode. Count the tweens on each dot. The first lane's dot has two.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
tweens on the useEffect dot: 2
tweens on the useGSAP dot: 1Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Start a tween from a click without contextSafe
Write onClick={() => gsap.to(".dot", { scale: 1.5, repeat: -1, yoyo: true })} and then remove the component.
Myth vs fact
Myth
A useEffect is enough for GSAP in React.
Fact
It works only if you return a cleanup that reverts a context. useGSAP does that for you, plus scoping and context-safe handlers.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
useGSAPusesuseLayoutEffectin the browser (useEffecton the server), so afrom()inside it writes its start state before the first paint: no flash of the finished state.
Remember this
- 1
The problem this solves: React creates and removes elements all the time, and in development StrictMode runs every effect twice to expose missing cleanup. A
useEffectthat starts a tween and returns nothing leaves a tween per run, and animations on removed elements keep running (Topic 14.5). Writing the context and revert by hand in every component is boilerplate. - 2
The hook.
useGSAP(() => { … }, { scope: ref, dependencies: [x], revertOnUpdate: true }). The callback runs in a layout effect (after the DOM update, before paint) inside agsap.contextscoped to the ref's element, so".dot"means a.dotinside this component. With nodependenciesit runs once after mount, and the context is reverted on unmount. - 3
Dependencies. When
dependencieschange, the callback runs again. The earlier animations are reverted only on unmount, so a new tween can stack on the old.revertOnUpdate: truereverts the previous run first, like auseEffectcleanup: use it when the callback builds everything from state. - 4
**Event handlers need
contextSafe.** Code that runs later, from a click, is outside the context, so its tweens would outlive the component.const { contextSafe } = useGSAP({ scope })returns a wrapper:onClick={contextSafe(() => gsap.to(…))}adds the handler's tweens to the context. - 5
StrictMode. In development React mounts, unmounts and mounts again.
useGSAPreverts the first run before the second; auseEffectwith no cleanup leaves two tweens (the second example counts them). Production runs effects once, so the leak hides until a component remounts.
Explain it without notes
Why does a useEffect that starts a GSAP tween leak in StrictMode, and how does useGSAP fix it?
What do scope, dependencies, revertOnUpdate and contextSafe do?
Practice
Write a Card component that fades its .line children in once on mount, scoped to the component.
Add a button to it that replays the animation on click, safe to unmount.
Done when you can
You use
useGSAPwith ascoperef instead of a bareuseEffectYou wrap event-handler animations in
contextSafeYou can explain what StrictMode's double run shows