How to stop a looping animation in React Native?

I like to encapsulate my animations in hooks, it makes for a much cleaner, easier, and more reusable pattern than class components. Here's how I do a looping animation with easy start/stop control in typescript:

export const useBounceRotateAnimation = (running: boolean = true, rate: number = 300) => {
    //Example of making an infinite looping animation and controlling it with a boolean
    //Note that this assumes the "rate" is constant -- if you wanted to change the rate value after creation the implementation would have to change a bit

    //Only create the animated value once
    const val = useRef(new Animated.Value(0))

    //Store a reference to the animation since we're starting-stopping an infinite loop instead of starting new animations and we aren't changing any animation values after creation. We only want to create the animation object once.
    const anim = useRef(
        Animated.loop(
            Animated.timing(val.current, {
                toValue: 1,
                duration: rate,
                easing: Easing.linear,
                useNativeDriver: true,
                isInteraction: false,
            })
        )
    ).current

    //Interpolate the value(s) to whatever is appropriate for your case
    const interpolatedY = val.current.interpolate({
        inputRange: [0, 0.5, 1],
        outputRange: [0, 6, 0],
    })
    const interpolatedRotate = val.current.interpolate({
        inputRange: [0, 0.25, 0.5, 1],
        outputRange: ["0deg", "-3deg", "3deg", "0deg"],
    })

    //Start and stop the animation based on the value of the boolean prop
    useEffect(() => {
        if (running) {
            anim.start()
        } else {
            //When stopping reset the value to 0 so animated item doesn't stop in a random position
            anim.stop()
            val.current.setValue(0)
        }

        //Return a function from useEffect to stop the animation on unmount
        return () => anim.stop()
     //This useEffect should rerun if "running" or "anim" changes (but anim won't change since its a ref we never modify)
    }, [running, anim])

     //Return the animated values. Use "as const" const assertion to narrow the output type to exactly the two values being returned. 
    return [interpolatedY, interpolatedRotate] as const
}

I use this actual hook implementation to make a character image "walk" in my game. Now in my component it's very easy to use:

const MyComponent = () => {
    const [isRunning, setIsRunning] = useState(true)

    const [translation, rotation] = useBounceRotateAnimation(isRunning)

    return (
        <Animated.View
            style={{
                transform: [{ translateY: translation}, { rotate: rotation}],
            }}
        >
             ....rest of your component here, just setIsRunning(false) to stop animation whenever you need
        </Animated.View>
    )
}

As you can see this pattern is very clean and reusable. The animated component doesn't need to know anything about the animation, it just consumes the animated values and tells the animation when to run.


Based on my comment in Nguyên Hoàng's answer. Here is another way to stop the looping animation if you call this.state.angle.stopAnimation():

runAnimation() {
  this.state.angle.setValue(0);
  Animated.timing(this.state.angle, {
    toValue: 360,
    duration: 8000,
    easing: Easing.linear
  }).start((o) => {
    if(o.finished) {
      this.runAnimation();
    }
  });
}

You can create a variable stopAnimation to stop Animation whenever you want by only when stopAnimation === false then callback runAnimation function. Like example:

this.state = { stopAnimation: false }

runAnimation() {
  this.state.spinValue.setValue(0);
  Animated.timing(
    this.state.spinValue,
    {
      toValue: 1,
      duration: 3000,
      easing: Easing.linear
    }
  ).start( () => {
    if(this.state.stopAnimation === false) {
      this.runAnimation();
    }
  });
}

So you just need to create a button which call function this.state = { stopAnimation: true } to stop Animation.

Example here: https://rnplay.org/apps/Lpmh8A.