Missing a comma in changing my CLASS code to FUNCTIONAL code

Viewed 126

I am having trouble when trying to convert this function from a Class based one to a Functional based one. In the _spring() i seem to have an issue using the useState() within the context of an Animated component.

CLASS Style

 constructor(props) {
        super(props);
        this.springValue = new Animated.Value(100);
    }

 state = {
        currentIndex: 1,
        backClickCount: 0,
    }
 componentDidMount() {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
    }

    componentWillUnmount() {
        BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
    }

    handleBackButton = () => {
        this.state.backClickCount == 1 ? BackHandler.exitApp() : this._spring();
        return true;
    };

 _spring() {
        this.setState({ backClickCount: 1 }, () => {
            Animated.sequence([
                Animated.spring(
                    this.springValue,
                    {
                        toValue: -.07 * height,
                        friction: 5,
                        duration: 300,
                        useNativeDriver: true,
                    }
                ),
                Animated.timing(
                    this.springValue,
                    {
                        toValue: 100,
                        duration: 300,
                        useNativeDriver: true,
                    }
                ),
            ]).start(() => {
                this.setState({ backClickCount: 0 });
            });
        });
    }

My attempt at moving it to FUNCTIONAL style is:

 const [currentIndex, setCurrentIndex] = useState(1);
    const [backClickCount, setBackClickCount] = useState(0);

    const springValue = new Animated.Value(100);

 const handleBackButton = () => {
        backClickCount == 1 ? BackHandler.exitApp() : _spring();
        return true;
    };

    useEffect(() => {
        const backHandler = BackHandler.addEventListener(
            "hardwareBackPress",
            () => {
                handleBackButton();
            }
        );
        return () => backHandler.remove(); 
    }, []);

     const _spring = () => {**
         setBackClickCount(1), () => {
            Animated.sequence([
                Animated.spring(
                    springValue,
                    {
                        toValue: -.07 * height,
                        friction: 5,
                        duration: 300,
                        useNativeDriver: true,
                    }
                ),
                Animated.timing(
                    springValue,
                    {
                        toValue: 100,
                        duration: 300,
                        useNativeDriver: true,
                    }
                ),
            ]).start(() => {
                setBackClickCount(0)
            });
        });
    }

I've lost a comma in making the transition (see the **). How should this function look, as i feel like i'm very close. Would it be better to put it in a useEffect()? If so, how?

1 Answers

In your class component you are using the callback function for the setState function of a react state.

This is for the following usecase.

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied

This second callback argument of the setState function was removed for the useState hook for react functional components. However, you can use a useEffect with the state as a dependency in order to react on state changes which will guarantee that the state already contains the updated value. This is by design of the framework and is recommended here in the official documentation.

In your case, you must be very careful! You are setting the same state backClickCount in your callback function again (in the start callback). If we just use the same code in a useEffect and react on backClickCount changes, then this would be an infinite loop. Thus, we must guard this. Since you are setting backClickCount equal to one in the _spring function, then start the animation, and then set the backClickCount back to zero, we can guard this in the useEffect with a simple if statement in order to prevent an infinite loop. If backClickCount is equal to zero, do something, set the state to zero. This will fire the useEffect again but this time it won't run again.

That being said, we can translate your missing code of your functional component as follows.

const [backClickCount, setBackClickCount] = useState(0);

React.useEffect(() => {
 if (backClickCount === 1) {
   Animated.sequence([
        Animated.spring(
           springValue,
              {
                 toValue: -.07 * height,
                 friction: 5,
                 duration: 300,
                 useNativeDriver: true,
               }
           ),
        Animated.timing(
              springValue,
                 {
                   toValue: 100,
                   duration: 300,
                   useNativeDriver: true,
                 }
        ),
     ]).start(() => {
          setBackClickCount(0)
      });
   });
  }
}, [backClickCount])

const _spring = () => {
   setBackClickCount(1)
}

Remarks: Some explanation if you are unfamiliar with useEffect. The useEffect takes two arguments. A function and an array. The array holds the dependencies of the hook. If one of the values inside the dependency array changes, the function provided will be called. Thus, if the states value is updated, it will be fired. This enables us to perform side effects on a state change. A common pitfall in react are infinite loops using this pattern. Usually this occurs if we set a state inside the useEffect and have its state value inside the dependency array of the same useEffect. If we do not guard this properly, this will lead to an infinite loop.

If the dependency array is empty, then the useEffect will be called exactly once onMount of the react component.

Related