infodev
infodev

Reputation: 5235

Simple time counter in react

I'm starting to learn React and I want to create a component that compares the current time to starting time and update the view.

Here's how I make it:

import React from 'react';

const Timer = ({ callQueuedTime }) => (

    setInterval(function () { 
        console.log("test"); 
        return <p>{new Date().getTime() - new Date(callQueuedTime).getTime()}</p> 
    }, 1000)
)

export default Timer;

In devTools, the console.log is refreshing every 1 sec but not the HTML. Also, I'm always getting the same value, which is not the value of new Date().getTime() - new Date(callQueuedTime).getTime()

For information callQueuedTime= 2020-04-23T22:02:07.382Z

UPDATE

I have updated my component code:

class Timer extends React.Component {
    constructor(props, context) {
        super(props, context)
        this.state = {
            timer: 0,
        }

    }
    componentDidMount() {
        console.log(this.props)
        setInterval(function () {
            const time = new Date().getTime() - new Date(this.props.callQueuedTime).getTime()
            this.setState({ time });
        }, 1000)
    }
    render() {
        return (
            <p>{this.state.timer}</p>
        );
    }
}

Now, after the first second, I get this error:

Cannot read property 'callQueuedTime' of undefined

Upvotes: 2

Views: 3534

Answers (1)

Zachary Haber
Zachary Haber

Reputation: 11017

You can definitely do this with a functional component.

As noted, you can't return JSX from a setInterval call. setInterval returns an id to use to clear.

The way I go about this is to keep the time as state, and then update that time every second in an effect.

import React, { useEffect, useState } from 'react';

const Timer = ({ callQueuedTime }) => {
  const [time, setTime] = useState(() => new Date().getTime());
  useEffect(() => {
    const queuedTime = new Date(callQueuedTime).getTime();
    const intervalId = setInterval(function () {
      setTime(new Date().getTime() - queuedTime);
    }, 1000);
    return ()=>{
      clearInterval(intervalId);
    }
  }, [callQueuedTime]);
  return <p>{time}</p>;
};

export default Timer;

The function being returned in the useEffect is the cleanup function. It is called before the useEffect runs the next time and before the component unmounts. For further reading:

https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup

https://overreacted.io/a-complete-guide-to-useeffect/

Here's the working code as a class component:

The issue you were running into was that you were using a function in the setInterval rather than an arrow function, so you were losing access to your component's this. The other issue you would run into is that you weren't cleaning up your interval on unmount.

import React, { Component } from "react";

export default class TimerComponent extends React.Component {
  state = { time: 0 };

  componentDidMount() {
    this.intervalId = setInterval(() => {
            const time =
        new Date().getTime() - new Date(this.props.callQueuedTime).getTime();
      this.setState({ time });
    }, 1000);
  }
  componentWillUnmount(){
    clearInterval(this.intervalId);
  }
  render() {
    return <p>{this.state.time}</p>;
  }
}

Here's a stackblitz showcasing both options:

https://stackblitz.com/edit/react-w2gqa6?file=Timer.jsx

Upvotes: 3

Related Questions