Maor Magori
Maor Magori

Reputation: 75

Why is useEffect not being triggered?

I have a functional component that is supposed to be a running clock:

import React,{useState,useEffect} from 'react';
import 'materialize-css/dist/css/materialize.min.css';
import { parseTime } from '../../Utils/utils'

const MainClock = (props) => {
    const [timeString, setTimeString] = useState(parseTime(new Date(), true));
    function tick(){
        console.log("TICK:" + timeString)
        setTimeString(parseTime(new Date(), true));
    };

    useEffect(()=>{console.log("rendered!");setTimeout(tick,500);},[timeString]);
    return (
        <div>
            <h5 className="center-align mainclock">{timeString}</h5>
        </div>        
    );
}
 
export default MainClock;

But for some reason it is only being rendered twice and the console output is:

rendered!
TICK:14:56:21
rendered!
TICK:14:56:22

Why isn't useeffect being called after the second render?

Any help is welcomed!

Edit: If it helps, this is parseTime:

const parseTime = (timeDate, withSeconds=false) =>{
    let time = timeDate.getHours()<10 ? `0${timeDate.getHours()}`:`${timeDate.getHours()}`;
    time+=":";
    time+= timeDate.getMinutes()<10 ? `0${timeDate.getMinutes()}`:`${timeDate.getMinutes()}`;
    if(withSeconds){
        time+=":";
        time+=timeDate.getSeconds()<10 ? `0${timeDate.getSeconds()}`:`${timeDate.getSeconds()}`;
    }
    return time;
}

Upvotes: 4

Views: 140

Answers (2)

Yousaf
Yousaf

Reputation: 29354

Problem is the use of setTimeout and the use of low delay, i.e 500ms for the timeout. If you log the return value of parseTime, you will notice that between the two calls, it returns the same time string, so state never updates, leading to the component never re-rendering and hence useEffect never executes again to set another setTimeout.

Solution

Increase the timeout delay or check the return value of the parseTime function and if its the same as the one in the state, call this function again.

Moreover, its more appropriate to use setInterval here instead of setTimeout because setInterval will only need to be called once and it will call the tick function repeatedly until the interval is cancelled. If you use setTimeout, then you will need to call setTimeout again and again to schedule a new tick function call.

Upvotes: 4

wangdev87
wangdev87

Reputation: 8751

As I indicated above, it is the problem of the short setTimeout timing value - 500ms. To make it work, you need to use setInterval

const MainClock = (props) => {
    const [timeString, setTimeString] = useState(parseTime(new Date(), true));
    function tick(){
        console.log("TICK:" + timeString)
        setTimeString(parseTime(new Date(), true));
    };

    useEffect(() => {
      setInterval(tick, 500);
    }, []);
    useEffect(()=>{console.log("rendered!");},[timeString]);
    return (
        <div>
            <h5 className="center-align mainclock">{timeString}</h5>
        </div>        
    );
}

Upvotes: 2

Related Questions