Where to apply my moment() function in a react component?

17,710

Solution 1

@KrzysztofSztompka is correct, but I would add that maintaining two separate state variables to represent the current date as a number and as a formatted string is an antipattern. Derived state variables (i.e., state variables that can be calculated using another state variable) increases the responsibility on the developer to always keep the two state variables in sync. That may not seem too difficult in this simple example, but it can become more difficult in larger, more complicated components/apps. Instead, it is generally considered better practice to maintain one source of truth and calculate any derived values on the fly as you need them. Here's how I would apply this pattern to your example.

import React from 'react';
import Moment from 'moment';

export default class Clock extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      dateTimestamp : Date.now()
    };
    this.tick = this.tick.bind(this);
  }

  tick() {
    this.setState({
      dateTimestamp: this.state.dateTimestamp + 1
    });
  }

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

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

  render() {
    // Calculate the formatted date on the fly
    const date = Moment(this.state.dateTimestamp).toString();
    return(
      <div className="clock"> Heure locale : {date}</div>
    );
  }

}

Solution 2

change your tick function to this:

tick = () => {
  var timestamp = this.state.dateTimestamp + 1;
  this.setState({
    dateTimestamp: timestamp,
    dateFormatted: Moment(timestamp).toString()
  });
}

This is because from docs :

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

Therefore in your next setState call it use old value. My proposition change this two values at once.

Share:
17,710
AtoM_84
Author by

AtoM_84

Updated on July 04, 2022

Comments

  • AtoM_84
    AtoM_84 almost 2 years

    I am trying to do a clock component, simply to give the date and time in local format in a webpage. I imported MomentJS using the command line npm i moment --save in my webpack environment. Next I wrote this in my Clock.jsx component (mostly based on the React example on the website).

    import React from 'react';
    import Moment from 'moment';
    
    export default class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          dateTimestamp : Date.now()
        };
      }
      tick = () => {
        this.setState({dateTimestamp: this.state.dateTimestamp + 1});
        console.log('tick');
      }
      componentDidMount() {
        this.interval = setInterval(this.tick, 1000);
      }
      componentWillUnmount() {
        clearInterval(this.interval);
      }
      render() {
        const date = this.state.dateTimestamp;
        return(
          <div className="clock"> Heure locale : {date}</div>
        );
    }
    
    }
    

    Doing this the timestamp incremented correctly. However, when passing a new state element in the object, the first value (based on Date.now() ) is calculated in the constructor but for each tick, only the timestamp is incrementing the formatted date is stuck on its first value. Here is the code.

    import React from 'react';
    import Moment from 'moment';
    
    export default class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          dateTimestamp : Date.now(),
          dateFormatted : Moment(Date.now()).toString()
        };
      }
      tick = () => {
        this.setState({dateTimestamp: this.state.dateTimestamp + 1});
        console.log(this.state.dateTimestamp);
        this.setState({dateFormatted: Moment(this.state.dateTimestamp).toString()});
        console.log(this.state.dateFormatted);
      }
      ...
      render() {
        const date = this.state.dateFormatted;
        return(
          <div className="clock"> Heure locale : {date}</div>
        );
    }
    
    }
    

    Does anyone could explain help me solving this issue but above all tell me what is going wrong with my piece of code?

    Thanks

    UPDATE: In the end my use of moment was not appropriate, even if I cannot figure out why it would not work this way. Find below my correct implementation to have the date and time refreshed every seconds.

    import React from 'react';
    import Moment from 'moment';
    
    export default class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          dateFormatted : Moment().locale('fr').format('dddd Do MMMM YYYY HH:mm:ss').toString()
        };
      }
      tick = () => {
        this.setState({
          dateFormatted : Moment().locale('fr').format('dddd Do MMMM YYYY HH:mm:ss').toString()
        });
      }
      componentDidMount() {
        this.interval = setInterval(this.tick, 1000);
      }
      componentWillUnmount() {
        clearInterval(this.interval);
      }
      render() {
        const date = this.state.dateFormatted;
        return(
          <div className="clock"> Date (locale) : {date}</div>
        );
      }
    }
    

    This also "solves" the anti pattern issue exposed below (different cross-dependant setState() call). I would need the timestamp for any other reason but I will find a workaround.