How to execute two animations sequentially, using react-spring?

Viewed 2766

I tried chaining two springs (using useChain), so that one only starts after the other finishes, but they are being animated at the same time. What am I doing wrong?

import React, { useRef, useState } from 'react'
import { render } from 'react-dom'
import { useSpring, animated, useChain } from 'react-spring'

function App() {
  const [counter, setCounter] = useState(0)
  const topRef = useRef()
  const leftRef = useRef()
  const { top } = useSpring({ top: (window.innerHeight * counter) / 10, ref: topRef })
  const { left } = useSpring({ left: (window.innerWidth * counter) / 10, ref: leftRef })

  useChain([topRef, leftRef])

  return (
    <div id="main" onClick={() => setCounter((counter + 1) % 10)}>
      Click me!
      <animated.div id="movingDiv" style={{ top, left }} />
    </div>
  )
}

render(<App />, document.getElementById('root'))

Here's a codesandbox demonstrating the problem: https://codesandbox.io/s/react-spring-usespring-hook-m4w4t

2 Answers

I did some digging as this was puzzling me as well and came across this spectrum chat.

I'm not sure I totally understand what is going on but it seems the current value of the refs in your code is only read once, and so when the component mounts, the chain is completed instantly and never reset. Your code does work if you put in hardcoded values for the two springs and then control them with turnaries but obviously you are looking for a dynamic solution.

I've tested this and it seems to do the job:

const topCurrent = !topRef.current ? topRef : {current: topRef.current};
const leftCurrent = !leftRef.current ? leftRef : {current: leftRef.current};

useChain([topCurrent, leftCurrent]);

It forces the chain to reference the current value of the ref each time. The turnary is in there because the value of the ref on mount is undefined - there may be a more elegant way to account for this.

I just found out that there's a much simpler solution, using only useSpring:

function App() {
  const [counter, setCounter] = useState(0)

  const style = useSpring({
    to: [
      { top: (window.innerHeight * counter) / 5 },
      { left: (window.innerWidth * counter) / 5 }
    ]
  })

  return (
    <div id="main" onClick={() => setCounter((counter + 1) % 5)}>
      Click me!
      <animated.div id="movingDiv" style={style} />
    </div>
  )
}

Example: https://codesandbox.io/s/react-spring-chained-animations-8ibpi

Related