Search code examples
javascriptreact-nativeperformanceexporeact-animated

react-animated loop becomes very buggy after a short time, React Native, Animated.Text


I recently started trying to use animation in an app I am trying to make in React Native. I am not 100% familiar with React Animated, but I believe the animations that I am trying to make are very simple.

I have a screen where I would like some text to slide in from the right, pause a few seconds, slide out to the left, before repeating itself with some other text.

While I did manage to do that, the animation and the text quickly become very buggy (aka no smooth animation, no animation at all after a while, the text will very quickly change randomly, etc...).

I am not sure why this is, I tried switching the useNativeDriver to true to hopefully get a smoother animation, but then I get an error saying I can't use the style property 'left'.

Here is the code:

import React, { useState, useCallback, useEffect } from 'react';
import { Text, View, StyleSheet, Animated } from 'react-native';
import Constants from 'expo-constants';

function App() {
  let [wordsAnim] = useState(new Animated.Value(60)),
    runAnimation = () => {
      wordsAnim.setValue(60);
      Animated.sequence([
        Animated.timing(wordsAnim, {
          toValue: 0,
          duration: 1500,
          useNativeDriver: false,
        }),
        Animated.timing(wordsAnim, {
          toValue: -60,
          duration: 1500,
          delay: 3000,
          useNativeDriver: false,
        }),
      ]).start(({ finished }) => {
        runAnimation();
        updateWord();
      });
    };

  //An array of random words to display
  const words = ['First', 'Second', 'Third', 'Fourth'];

  //First word displayed is a random word from the array
  const [word, changeWord] = useState(
    words[Math.floor(Math.random() * words.length)]
  );

  //Update the word displayed with another random word from the array
  const updateWord = () => {
    const index = Math.floor(Math.random() * words.length);
    changeWord(words[index]);
  };

  useEffect(() => {
    runAnimation();
  });

  return (
    <View style={styles.container}>
      <Animated.Text
        style={{
          margin: 24,
          fontSize: 18,
          fontWeight: 'bold',
          textAlign: 'center',
          left: wordsAnim.interpolate({
            inputRange: [0, 100],
            outputRange: ['0%', '100%'],
          }),
        }}>
        {word}
      </Animated.Text>
    </View>
  );
}

export default App;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

You can also find this example here.

I would also take any tips regarding animations or anything else for React Native, Thanks!!


Solution

  • As pointed out by @David784 , my runAnimation was retriggered after every completed render. It should've only be run on component mounts. Adding a few callbacks did the trick!

    Working code below:

    import React, { useState, useCallback, useEffect } from 'react';
    import { Text, View, StyleSheet, Animated } from 'react-native';
    import Constants from 'expo-constants';
    
    function App() {
      let [wordsAnim] = useState(new Animated.Value(60)),
        runAnimation = useCallback(() => {
          wordsAnim.setValue(60);
          Animated.sequence([
            Animated.timing(wordsAnim, {
              toValue: 0,
              duration: 1500,
              useNativeDriver: false,
            }),
            Animated.timing(wordsAnim, {
              toValue: -60,
              duration: 1500,
              delay: 3000,
              useNativeDriver: false,
            }),
          ]).start(({ finished }) => {
            updateWord();
            runAnimation();
          });
        }, [updateWord, wordsAnim]);
    
      //An array of random words to display
      const words = ['First', 'Second', 'Third', 'Fourth'];
    
      //First word displayed is a random word from the array
      const [word, changeWord] = useState(
        words[Math.floor(Math.random() * words.length)]
      );
    
      //Update the word displayed with another random word from the array
      const updateWord = useCallback(() => {
        const index = Math.floor(Math.random() * words.length);
        changeWord(words[index]);
      }, [words]);
    
      useEffect(() => {
        runAnimation();
      }, [runAnimation]);
    
      return (
        <View style={styles.container}>
          <Animated.Text
            style={{
              margin: 24,
              fontSize: 18,
              fontWeight: 'bold',
              textAlign: 'center',
              left: wordsAnim.interpolate({
                inputRange: [0, 100],
                outputRange: ['0%', '100%'],
              }),
            }}>
            {word}
          </Animated.Text>
        </View>
      );
    }
    
    export default App;
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        paddingTop: Constants.statusBarHeight,
        backgroundColor: '#ecf0f1',
        padding: 8,
      },
    });