Mettre à jour le composant React toutes les secondes

114

J'ai joué avec React et j'ai le composant temporel suivant qui Date.now()s'affiche simplement à l'écran:

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;

Quelle serait la meilleure façon de mettre à jour ce composant toutes les secondes pour redessiner l'heure du point de vue React?

flocon de neige
la source

Réponses:

152

Vous devez utiliser setIntervalpour déclencher le changement, mais vous devez également effacer le minuteur lorsque le composant se démonte pour éviter qu'il ne laisse des erreurs et ne fuit de mémoire:

componentDidMount() {
  this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
  clearInterval(this.interval);
}
Waiski
la source
2
Si vous souhaitez une bibliothèque qui encapsule ce genre de chose, j'ai faitreact-interval-rerender
Andy
J'ai ajouté ma méthode setInterval dans mon constructeur et cela fonctionnait mieux.
aqteifan
89

Le code suivant est un exemple modifié du site Web React.js.

Le code original est disponible ici: 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')
);
M. Polywhirl
la source
1
Pour une raison quelconque, j'obtiens this.updater.enqueueSetState n'est pas une erreur de fonction lors de l'utilisation de cette approche. Le rappel setInterval est correctement lié au composant this.
baldrs
16
Pour les idiots comme moi: ne nommez pas la minuterie updatercar elle ruine le cycle de mise à jour
Baldrs
3
Avec ES6, je dois changer une ligne comme celle-ci.interval = setInterval (this.tick.bind (this), 1000);
Kapil
Pouvez-vous ajouter le lien vers l'URL qui fait référence? Merci ~
frederj
J'ai ajouté une méthode de formatage et une propriété "constructeur" pour plus de robustesse.
M. Polywhirl
15

@Waisky a suggéré:

Vous devez utiliser setIntervalpour déclencher le changement, mais vous devez également effacer le minuteur lorsque le composant se démonte pour éviter qu'il ne laisse des erreurs et ne fuit de mémoire:

Si vous souhaitez faire la même chose, en utilisant Hooks:

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

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

Concernant les commentaires:

Vous n'avez rien à faire passer à l'intérieur []. Si vous passez timeentre parenthèses, cela signifie exécuter l'effet à chaque fois que la valeur de timechange, c'est-à-dire qu'il en invoque un nouveau à setIntervalchaque fois, timechange, ce qui n'est pas ce que nous recherchons. Nous souhaitons n'appeler setIntervalqu'une seule fois lorsque le composant est monté, puis setIntervalappeler setTime(Date.now())toutes les 1000 secondes. Enfin, nous invoquonsclearInterval lorsque le composant est démonté.

Notez que le composant est mis à jour, en fonction de la façon dont vous l'avez utilisé time, à chaque fois que la valeur des timechangements. Cela n'a rien à voir avec la mise timeen place []de useEffect.

1 homme
la source
1
Pourquoi avez-vous passé un tableau vide ( []) comme deuxième argument à useEffect?
Mekeor Melire
La documentation React sur le hook d'effet nous dit: «Si vous voulez exécuter un effet et le nettoyer une seule fois (lors du montage et du démontage), vous pouvez passer un tableau vide ([]) comme deuxième argument.» Mais OP veut que l'effet s'exécute toutes les secondes, c'est-à-dire qu'il se répète, c'est-à-dire pas une seule fois.
Mekeor Melire
La différence que j'ai remarquée si vous passez [time] dans le tableau, cela créera et détruira le composant à chaque intervalle. Si vous passez le tableau vide [], il continuera d'actualiser le composant et ne le démontera que lorsque vous quitterez la page. Mais, dans les deux cas, pour le faire fonctionner, j'ai dû ajouter key = {time} ou key = {Date.now ()} pour qu'il soit réellement rendu.
Teebu le
8

Dans la componentDidMountméthode du cycle de vie du composant , vous pouvez définir un intervalle pour appeler une fonction qui met à jour l'état.

 componentDidMount() {
      setInterval(() => this.setState({ time: Date.now()}), 1000)
 }
erik-sn
la source
4
Correct, mais comme le suggère la première réponse, n'oubliez pas d'effacer l'heure pour éviter les fuites de mémoire: componentWillUnmount () {clearInterval (this.interval); }
Alexander Falk
3
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>
        );
      }
    }
Jagadeesh
la source
0

Vous étiez donc sur la bonne voie. À l'intérieur de votre, componentDidMount()vous auriez pu terminer le travail en implémentant setInterval()pour déclencher le changement, mais rappelez-vous que la façon de mettre à jour l'état d'un composant est via setState(), donc à l'intérieur de vous, componentDidMount()vous auriez pu faire ceci:

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

De plus, vous utilisez Date.now()ce qui fonctionne, avec l' componentDidMount()implémentation que j'ai proposée ci-dessus, mais vous obtiendrez un long ensemble de mises à jour de nombres désagréables qui ne sont pas lisibles par l'homme, mais c'est techniquement le temps de mise à jour toutes les secondes en millisecondes depuis le 1er janvier 1970, mais nous veulent rendre ce temps lisible à la façon dont nous, les humains, lisons le temps, donc en plus d'apprendre et de mettre en œuvre, setIntervalvous voulez en savoir plus surnew Date() et toLocaleTimeString()et vous mettre en œuvre comme ceci:

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

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

Remarquez que j'ai également supprimé la constructor()fonction, vous n'en avez pas forcément besoin, mon refactor équivaut à 100% à l'initialisation du site avec la constructor()fonction.

Daniel
la source
0

En raison des changements dans React V16 où componentWillReceiveProps () a été obsolète, c'est la méthodologie que j'utilise pour mettre à jour un composant. Notez que l'exemple ci-dessous est dans Typescript et utilise la méthode statique getDerivedStateFromProps pour obtenir l'état initial et l'état mis à jour chaque fois que les Props sont mis à jour.

    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>;
  }
}
Jarora
la source