Dev Man
Dev Man

Reputation: 71

useState acting out when trying to update with a setTimeout

I am trying to use setTimeout to flip a useState between true and false every 6 seconds. I use this state to add/ remove a class from a div - this will cause the div to toggle between top: 0 to top: 100% (and a transition takes care of the animation).

To be more specific, I have an iPhone wrapper image with a content image inside it. The idea is that it will slowly scroll to the bottom of the content image, and then after 6 seconds (from the time it started scrolling down), it will then start scrolling back up, ad infinitum. However, it's not working right at all.

I've tested it with an onClick and it works exactly as I intend. However, with the setTimeout logic:

Of course, it cannot be true and false, and it should be flipping its value. Could someone tell me why it's not working and perhaps tell me why it's acting in this bizarre way?

import React, { useState } from 'react';
import iphone from '../Images/iphone.png'

const Iphone = (props) => {
    const [isInitialised, setInitialised] = useState(false)
    const [animating, setAnimating] = useState(false)

    const startAnimation = () => {
        setAnimating(!animating); /* Even if I use `true`, it will log to the console as `false` */
        console.warn('animation change iphone!');
        console.warn(animating);
        console.warn(isInitialised); /* This also always logs as `false` */

        setTimeout(() => {
            startAnimation();
        }, 6000);
    }

    if (!isInitialised) {
        setInitialised(true);
        startAnimation();
    }

    return (
        <div className={`iphone align-mobile-center ${animating ? "iphone--animating" : ""}`} onClick={() => setAnimating(!animating)}>
            <img className="iphone__image" src={iphone} alt="An iPhone" />
            <div className="iphone__content">
                <img className="iphone__content-image" src={props.image} alt={props.alt} />
            </div>
        </div>
    )
}

export default Iphone;

I use the isInitialised otherwise it seems to hit an infinite loop.

Upvotes: 1

Views: 289

Answers (2)

Amit Chauhan
Amit Chauhan

Reputation: 6869

You can do something like this. useEffect with empty array as deps so it will only once, you don't need isInitialized state.

In useEffect use setInterval so it will run every 6 seconds.

use callback way to setting state so you always get the correct value of animating.

import React, { useState } from 'react';
import iphone from '../Images/iphone.png'

const Iphone = (props) => {
    const [animating, setAnimating] = useState(false)

    useEffect(() => {
      const startAnimation = () => {
        setAnimating(v => !v);
      }
      const interval = setInterval(() => {
        startAnimation();
      }, 6000);
      return () => clearInterval(interval);
    }, []);

    return (
        <div className={`iphone align-mobile-center ${animating ? "iphone--animating" : ""}`} onClick={() => setAnimating(!animating)}>
            <img className="iphone__image" src={iphone} alt="An iPhone" />
            <div className="iphone__content">
                <img className="iphone__content-image" src={props.image} alt={props.alt} />
            </div>
        </div>
    )
}

export default Iphone;

Upvotes: 2

Hai Tien
Hai Tien

Reputation: 3117

you can use useEffect and bring setTimeout outside.

import React, { useState } from 'react';
import iphone from '../Images/iphone.png'

const Iphone = (props) => {
    const [isInitialised, setInitialised] = useState(false)
    const [animating, setAnimating] = useState(false)
    useEffect(()=>{
       startAnimation();
    },[]);
    const startAnimation = () => {
        setAnimating(!animating); 
        console.warn('animation change iphone!');
        console.warn(animating);
        console.warn(isInitialised);
    }

    setTimeout(() => {
      if (!isInitialised) {
        setInitialised(true);
        startAnimation();
      }
    }, 6000);



    return (
        <div className={`iphone align-mobile-center ${animating ? "iphone--animating" : ""}`} onClick={() => setAnimating(!animating)}>
            <img className="iphone__image" src={iphone} alt="An iPhone" />
            <div className="iphone__content">
                <img className="iphone__content-image" src={props.image} alt={props.alt} />
            </div>
        </div>
    )
}

export default Iphone;

Upvotes: 1

Related Questions