Actualizar el componente React cada segundo

114

He estado jugando con React y tengo el siguiente componente de tiempo que solo se muestra Date.now()en la pantalla:

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;

¿Cuál sería la mejor manera de hacer que este componente se actualice cada segundo para volver a dibujar el tiempo desde la perspectiva de React?

asesino de copos de nieve
fuente

Respuestas:

151

setIntervalDebe utilizar para activar el cambio, pero también debe borrar el temporizador cuando el componente se desmonta para evitar que deje errores y fugas de memoria:

componentDidMount() {
  this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
  clearInterval(this.interval);
}
Waiski
fuente
2
Si desea una biblioteca que encapsule este tipo de cosas, hicereact-interval-rerender
Andy
Agregué mi método setInterval en mi constructor y funcionó mejor.
aqteifan
89

El siguiente código es un ejemplo modificado del sitio web React.js.

El código original está disponible aquí: 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')
);
Sr. Polywhirl
fuente
1
Por alguna razón, obtengo this.updater.enqueueSetState no es un error de función cuando uso este enfoque. La devolución de llamada setInterval está correctamente vinculada al componente this.
calvos
16
Para idiotas como yo: no nombre el temporizador updaterporque arruina el ciclo de actualización
baldrs
3
Con ES6 necesito cambiar una línea como this.interval = setInterval (this.tick.bind (this), 1000);
Kapil
¿Puede agregar el enlace a la URL que hace referencia? Gracias ~
frederj
Agregué un método de formato y una propiedad de "constructor" para mayor robustez.
Mr. Polywhirl
15

@Waisky sugirió:

setIntervalDebe utilizar para activar el cambio, pero también debe borrar el temporizador cuando el componente se desmonta para evitar que deje errores y fugas de memoria:

Si desea hacer lo mismo, use Hooks:

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

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

Respecto a los comentarios:

No es necesario pasar nada por el interior []. Si pasa timeentre corchetes, significa ejecutar el efecto cada vez que el valor de los timecambios, es decir, invoca un nuevo setIntervalcada vez, timecambia, que no es lo que estamos buscando. Solo queremos invocar setIntervaluna vez cuando el componente se monta y luego setIntervalllama setTime(Date.now())cada 1000 segundos. Finalmente, invocamos clearIntervalcuando el componente está desmontado.

Tenga en cuenta que el componente se actualiza, en función de cómo lo haya utilizado time, cada vez que timecambia el valor . Eso no tiene nada que ver con poner timeen []de useEffect.

1hombre
fuente
1
¿Por qué pasó una matriz vacía ( []) como segundo argumento a useEffect?
Mekeor Melire
La documentación de React sobre el gancho de efectos nos dice: "Si desea ejecutar un efecto y limpiarlo solo una vez (al montar y desmontar), puede pasar una matriz vacía ([]) como segundo argumento". Pero OP quiere que el efecto se ejecute cada segundo, es decir, que se repita, es decir, no solo una vez.
Mekeor Melire
La diferencia que noté si pasa [tiempo] en la matriz, creará y destruirá el componente en cada intervalo. Si pasa la matriz vacía [], seguirá actualizando el componente y solo lo desmontará cuando salga de la página. Pero, en cualquier caso, para que funcione, tuve que agregar key = {time} o key = {Date.now ()} para que se volviera a renderizar.
Teebu
8

En el componentDidMountmétodo del ciclo de vida del componente , puede establecer un intervalo para llamar a una función que actualiza el estado.

 componentDidMount() {
      setInterval(() => this.setState({ time: Date.now()}), 1000)
 }
erik-sn
fuente
4
Correcto, pero como sugiere la respuesta principal, recuerde borrar el tiempo para evitar pérdidas de memoria: 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
fuente
0

Entonces estabas en el camino correcto. Dentro de su componentDidMount(), podría haber terminado el trabajo implementando setInterval()para activar el cambio, pero recuerde que la forma de actualizar el estado de un componente es a través de setState(), por lo que dentro de su componentDidMount()podría haber hecho esto:

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

Además, usa el Date.now()que funciona, con la componentDidMount()implementación que ofrecí anteriormente, pero obtendrá un conjunto largo de números desagradables que se actualizan que no son legibles por humanos, pero técnicamente es el tiempo que se actualiza cada segundo en milisegundos desde el 1 de enero de 1970, pero nosotros quiere hacer de este tiempo de lectura a la forma en que los humanos tiempo de lectura, por lo que además de aprender e implementar setIntervaldesea aprender sobre new Date()y toLocaleTimeString()y desea ponerlo en práctica, así:

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

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

Tenga en cuenta que también eliminé la constructor()función, no necesariamente la necesita, mi refactor es 100% equivalente a inicializar el sitio con la constructor()función.

Daniel
fuente
0

Debido a los cambios en React V16 donde componentWillReceiveProps () ha quedado obsoleto, esta es la metodología que utilizo para actualizar un componente. Observe que el siguiente ejemplo está en Typecript y usa el método estático getDerivedStateFromProps para obtener el estado inicial y el estado actualizado cada vez que se actualizan los Props.

    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
fuente