Search code examples
javascriptreactjsuse-effectreact-componentuse-state

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;
}

Solution

  • 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.