205

I have been playing around with React and have the following time component that just renders Date.now() to the screen:

import React, { Component } from 'react';

class TimeComponent extends Component {
  constructor(props){
    super(props);
    this.state = { time: Date.now() };
  }
  render(){
    return(
      <div> { this.state.time } </div>
    );
  }
  componentDidMount() {
    console.log("TimeComponent Mounted...")
  }
}

export default TimeComponent;

What would be the best way to get this component to update every second to re-draw the time from a React perspective?

9 Answers 9

230

You need to use setInterval to trigger the change, but you also need to clear the timer when the component unmounts to prevent it leaving errors and leaking memory:

componentDidMount() {
  this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
  clearInterval(this.interval);
}
Sign up to request clarification or add additional context in comments.

2 Comments

If you would like a library that encapsulates this kind of thing, I made react-interval-rerender
I added my setInterval method in my constructor and that worked better.
147

@Waisky suggested:

You need to use setInterval to trigger the change, but you also need to clear the timer when the component unmounts to prevent it leaving errors and leaking memory:

If you'd like to do the same thing, using Hooks:

const [time, setTime] = useState(Date.now());

useEffect(() => {
  const interval = setInterval(() => setTime(Date.now()), 1000);
  return () => {
    clearInterval(interval);
  };
}, []);

Regarding the comments:

You don't need to pass anything inside []. If you pass time in the brackets, it means run the effect every time the value of time changes, i.e., it invokes a new setInterval every time, time changes, which is not what we're looking for. We want to only invoke setInterval once when the component gets mounted and then setInterval calls setTime(Date.now()) every 1000 seconds. Finally, we invoke clearInterval when the component is unmounted.

Note that the component gets updated, based on how you've used time in it, every time the value of time changes. That has nothing to do with putting time in [] of useEffect.

4 Comments

Why did you pass an empty array ([]) as second argument to useEffect?
The React documentation on the effect hook tells us: “If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument.” But OP wants the effect to run every second, i.e. reoccurring, i.e. not just once.
The difference I noticed if you pass in [time] in the array, it will create and destroy the component every interval. If you pass the empty array [], it will keep refreshing the component and only unmount it when you leave the page. But, in either case, to get it to work, I had to add key={time} or key={Date.now()} to get it to actually rerender.
If time is passed as a prop to a child component, that component will re-render whenever time changes. Or you can trigger a different effect with [time] as the second argument, and that effect will fire every time time gets a new value. The useEffect that sets the interval should have an empty array as the second argument, because it isn't updating, only setting/clearing the interval.
97

The following code is a modified example from React.js website.

Original code is available here: https://reactjs.org/#a-simple-component

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      seconds: parseInt(props.startTimeInSeconds, 10) || 0
    };
  }

  tick() {
    this.setState(state => ({
      seconds: state.seconds + 1
    }));
  }

  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  formatTime(secs) {
    let hours   = Math.floor(secs / 3600);
    let minutes = Math.floor(secs / 60) % 60;
    let seconds = secs % 60;
    return [hours, minutes, seconds]
        .map(v => ('' + v).padStart(2, '0'))
        .filter((v,i) => v !== '00' || i > 0)
        .join(':');
  }

  render() {
    return (
      <div>
        Timer: {this.formatTime(this.state.seconds)}
      </div>
    );
  }
}

ReactDOM.render(
  <Timer startTimeInSeconds="300" />,
  document.getElementById('timer-example')
);

5 Comments

I for some reason get this.updater.enqueueSetState is not a function error when using this approach. setInterval callback is properly bound to the component this.
For idiots like me: don't name timer updater because it ruins update cycle
With ES6 I need to change one line like this.interval = setInterval(this.tick.bind(this),1000);
Can you add the link to the url that references? Thanks~
I added a formatting method and a "constructor" property for more robustness.
9

In the component's componentDidMount lifecycle method, you can set an interval to call a function which updates the state.

 componentDidMount() {
      setInterval(() => this.setState({ time: Date.now()}), 1000)
 }

1 Comment

Correct, but as the top answer suggests, remember to clear the time to avoid memory leak: componentWillUnmount() { clearInterval(this.interval); }
5
class ShowDateTime extends React.Component {
   constructor() {
      super();
      this.state = {
        curTime : null
      }
    }
    componentDidMount() {
      setInterval( () => {
        this.setState({
          curTime : new Date().toLocaleString()
        })
      },1000)
    }
   render() {
        return(
          <div>
            <h2>{this.state.curTime}</h2>
          </div>
        );
      }
    }

Comments

3

i myself like setTimeout more that setInterval but didn't find a solution in class based component .you could use sth like this in class based components:

class based component and setInterval:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    };
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      this.state.date.toLocaleTimeString()
    );
  }
}

ReactDOM.render( 
  <Clock / > ,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app" />

function based component and setInterval:

https://codesandbox.io/s/sweet-diffie-wsu1t?file=/src/index.js

function based component and setTimeout:

https://codesandbox.io/s/youthful-lovelace-xw46p

Comments

0

So you were on the right track. Inside your componentDidMount() you could have finished the job by implementing setInterval() to trigger the change, but remember the way to update a components state is via setState(), so inside your componentDidMount() you could have done this:

componentDidMount() {
  setInterval(() => {
   this.setState({time: Date.now()})    
  }, 1000)
}

Also, you use Date.now() which works, with the componentDidMount() implementation I offered above, but you will get a long set of nasty numbers updating that is not human readable, but it is technically the time updating every second in milliseconds since January 1, 1970, but we want to make this time readable to how we humans read time, so in addition to learning and implementing setInterval you want to learn about new Date() and toLocaleTimeString() and you would implement it like so:

class TimeComponent extends Component {
  state = { time: new Date().toLocaleTimeString() };
}

componentDidMount() {
  setInterval(() => {
   this.setState({ time: new Date().toLocaleTimeString() })    
  }, 1000)
}

Notice I also removed the constructor() function, you do not necessarily need it, my refactor is 100% equivalent to initializing site with the constructor() function.

Comments

0

Owing to changes in React V16 where componentWillReceiveProps() has been deprecated, this is the methodology that I use for updating a component. Notice that the below example is in Typescript and uses the static getDerivedStateFromProps method to get the initial state and updated state whenever the Props are updated.

    class SomeClass extends React.Component<Props, State> {
  static getDerivedStateFromProps(nextProps: Readonly<Props>): Partial<State> | null {
    return {
      time: nextProps.time
    };
  }

  timerInterval: any;

  componentDidMount() {
    this.timerInterval = setInterval(this.tick.bind(this), 1000);
  }

  tick() {
    this.setState({ time: this.props.time });
  }

  componentWillUnmount() {
    clearInterval(this.timerInterval);
  }

  render() {
    return <div>{this.state.time}</div>;
  }
}

Comments

0

This can be implemented even with the setTimeout instead of setInterval. As the useState re-renders the component, it will call the setTimeout again and again. Here is my sample component which update the timer in every second. Also, Please let me know if I am making any mistake here.

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

export default function Footer() {
    const [seconds, setSeconds] = useState((new Date()).getSeconds());

    function GetTime() {
        setSeconds((new Date()).getSeconds());
        console.count(seconds);
    }

    setTimeout(() => {
        console.log("Footer Rendered");
        GetTime();
    }, 1000);

    return (
        <footer>
            <h2>Test Footer</h2>
            <p>Copyright &copy; {seconds}</p>
        </footer>
    )
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.