<div id="root" class="panel center"></div>
body{
  margin: 0;
  padding: 0;
  height: 100vh;
}

.app {
  padding: 10px;
}

.app > * + * {
  margin-top: 1rem
}
const { useEffect, useLayoutEffect, useRef, useState } = React;
console.clear();

function Box({ children }) {
  return <div className="box">{children}</div>;
}

function Circle({ children }) {
  return <div className="circle">{children}</div>;
}

function App() {
  const [reversed, setReversed] = useState(false);
  const app = useRef();
  // store the timeline in a ref.
  const tl = useRef();
      
  useLayoutEffect(() => {
    const ctx = gsap.context(() => {
      // add a box and circle animation to our timeline and play on first render
    console.log("creating timeline");
    tl.current && tl.current.progress(0).kill();
    tl.current = gsap.timeline()
      .to(".box", {
        rotation: 360
      })
      .to(".circle", {
        x: 100
      });
    }, app);
    return () => ctx.revert();
  }, []);
  
  useEffect(() => {
    // toggle the direction of our timeline
    console.log("toggling reverse to", reversed);
    tl.current.reversed(reversed);    
  }, [reversed]);
   
  return (
    <div className="app" ref={app}>
      <div>
        <button onClick={() => setReversed(!reversed)}>Toggle</button>
      </div>
      <Box>box</Box>
      <Circle>circle</Circle>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
View Compiled

External CSS

  1. https://codepen.io/GreenSock/pen/gOWxmWG.css

External JavaScript

  1. https://unpkg.co/gsap@3/dist/gsap.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js