Update child component DOM from parent on animation update

Viewed 444

I have set up the following Codepen for reference: Animate React Ref GSAP

I have a React project I'm working on in which, for sake of example, there exist 2 Components: a Pitch (as in a soccer pitch) and a Player. The Pitch is the parent to the Player, and a forwardRef() is used to manage the animation of each player that could be on the pitch from the Pitch component itself.

As you'll also see in the Player component render(). The SVG contains a <g id="Position">...</g> grouping in which the position of the circle should be updated as the animation progresses. This is where I'm at a loss.

In this project I'm using GSAP to manage the animations, but it could be any animation library really - maybe even none if I were to animate by re-render of Player components by state change, but performance may then be a worry. GSAP provides an onUpdate option to its timeline.to() function call. It could be possible to read the position values off the SVG from the ref but it would be bad practice to then try to update its DOM from the parent using ReactDOM.findDOMNode(component) [reference].

Thus, my questions really boil down to this:

  • If it is not best practice to affect the DOM of the child element (I believe it is not even possible from a functional component, according to ESLint on strict) directly from the parent, then how should I manage it?
    • Should the animation be triggered in the child?
    • Or, should I somehow re-render from the state of the parent?

If I haven't made myself perfectly clear please let me know and I'll do my best to clarify anything or everything!

1 Answers

There are few key principles that you should follow when you design React component.

  1. State is the source of truth, so how your component display (rendered) is entirely up to how data in state (& prop) looks like. There should never be a case that one state can render into different ways.

With that in mind, having this code timeline.to(playerRef.current, { x: 0, y: 0, duration: 2 }); is not really "The react way" because it doesn't store player position into a state, but use another thing (gsap) to manipulate how things display (x,y position)

This is more React way

// Pitch
const Pitch = () => {
  const defaultPosition = { x: 210, y: 136 }
  const [playerPosition, setPlayerPosition] = useState(defaultPosition);
  
  const playMe = () => {
    setPlayerPosition({ x: 0, y: 0 });
  };

  const resetMe = () => {
    setPlayerPosition(defaultPosition);
  };

  return (
    <>
      <button type="button" onClick={playMe}>Play</button>
      <button type="button" onClick={resetMe}>Reset</button>
      <hr />
      <svg width="420px" height="272px" viewBox="0 0 420 272">
        <g id="Pitch">
          <rect id="Pitch-Background" fill="#409D41" x="0" y="0" width="420" height="272"></rect>
        </g>
        <Player position={playerPosition} />
      </svg>
    </>
  );

}

So the next question will be "How to we manage animation then?"

90% of the animations in React Component, you will want to animation between states. A transition between state A to state B.

In terms of managing animation in react, I can break down into 3 groups.

  1. Transition from predefined state A to predefined state B (ex. div open x = 200 to div close x = 0)
  2. Transition from state A to state B dynamically (ex. move to where mouse click happen)
  3. A timeline/sequences of animation between state A to state B (ex. three elements has to animate one after another to fulfill the whole animation sequence)

If your use-case fall into 1. then css transition is your answer.

If your use-case fall into 2. then css transition can be your answer, or some animation library such as react-motion, react-spring, framer motion, etc.

If your use-case fall into 3. then GSAP Timeline is your big boy. BUT there's a certain way to write this. Which I won't get into detail at this point.


Again, in your example... let's say it's 2. (Transition from state A to state B dynamically). We can solve this with css transition.

const Player = ({ position }) => {
  return (
    <g
      style={{ transition: `all 1s ease-out` }}
      transform={`translate(${newPosition.x},${newPosition.y})`}
    >
      <circle id="Oval" fill="#FF0000" cx="23" cy="39" r="19.5"></circle>
    </g>
}

Here's the working code from what I describe. https://codepen.io/Doppy/pen/XWjNmdB

PS. I added in the example of how to make player move to a position of mouse click too :)

Have a good day!

Related