3

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

eduter
  • 71
  • 2
  • 5

2 Answers2

2

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

eduter
  • 71
  • 2
  • 5
1

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
lawrence-witt
  • 8,094
  • 3
  • 13
  • 32
  • 1
    Apparently, using this workaround in only one of the references is enough. I've even tried adding a 3rd spring and it still works: https://codesandbox.io/s/react-spring-usechain-hook-xww20 – eduter Apr 11 '20 at 19:50