setInterval en una aplicación React

101

Todavía soy bastante nuevo en React, pero he estado avanzando lentamente y me he encontrado con algo en lo que estoy atascado.

Estoy tratando de construir un componente de "temporizador" en React, y para ser honesto, no sé si lo estoy haciendo bien (o de manera eficiente). En mi código de abajo, me puse el estado para devolver un objeto { currentCount: 10 }y he estado jugando con componentDidMount, componentWillUnmounty rendery sólo puedo obtener el estado de "cuenta atrás" de 10 a 9.

Pregunta de dos partes: ¿Qué me estoy equivocando? Y, ¿hay una forma más eficiente de usar setTimeout (en lugar de usar componentDidMount& componentWillUnmount)?

Gracias de antemano.

import React from 'react';

var Clock = React.createClass({

  getInitialState: function() {
    return { currentCount: 10 };
  },

  componentDidMount: function() {
    this.countdown = setInterval(this.timer, 1000);
  },

  componentWillUnmount: function() {
    clearInterval(this.countdown);
  },

  timer: function() {
    this.setState({ currentCount: 10 });
  },

  render: function() {
    var displayCount = this.state.currentCount--;
    return (
      <section>
        {displayCount}
      </section>
    );
  }

});

module.exports = Clock;
Jose
fuente
2
bind(this)ya no es necesario, reaccionar lo hace por sí solo ahora.
Derek Pollard
2
su método de temporizador no actualiza currentCount
Bryan Chen
1
@Derek, ¿estás seguro? Acabo de hacer que el mío funcione agregando this.timer.bind(this)esto.El temporizador por sí solo no funcionó
el gusano
6
@Theworm @Derek está equivocado, algo así. React.createClass (que está en desuso) enlaza automáticamente los métodos, pero class Clock extends Componentno se enlaza automáticamente. Por lo tanto, depende de cómo esté creando sus componentes si necesita enlazar.
CallMeNorm

Respuestas:

157

Veo 4 problemas con su código:

  • En su método de temporizador, siempre está configurando su cuenta actual en 10
  • Intentas actualizar el estado en el método de renderizado
  • No usas el setStatemétodo para cambiar el estado
  • No está almacenando su intervalId en el estado

Intentemos arreglar eso:

componentDidMount: function() {
   var intervalId = setInterval(this.timer, 1000);
   // store intervalId in the state so it can be accessed later:
   this.setState({intervalId: intervalId});
},

componentWillUnmount: function() {
   // use intervalId from the state to clear the interval
   clearInterval(this.state.intervalId);
},

timer: function() {
   // setState method is used to update the state
   this.setState({ currentCount: this.state.currentCount -1 });
},

render: function() {
    // You do not need to decrease the value here
    return (
      <section>
       {this.state.currentCount}
      </section>
    );
}

Esto daría como resultado un temporizador que disminuye de 10 a -N. Si desea un temporizador que disminuya a 0, puede usar una versión ligeramente modificada:

timer: function() {
   var newCount = this.state.currentCount - 1;
   if(newCount >= 0) { 
       this.setState({ currentCount: newCount });
   } else {
       clearInterval(this.state.intervalId);
   }
},
dotnetom
fuente
Gracias. Esto tiene mucho sentido. Todavía soy un principiante y estoy tratando de entender cómo funciona el estado y qué va en qué "fragmentos", como renderizar.
José
Me pregunto, sin embargo, ¿es necesario usar componentDidMount y componentWillUnmount para establecer realmente el intervalo? EDITAR: Acabo de ver su edición más reciente. :)
Jose
@Jose Creo que componentDidMountes el lugar correcto para activar los eventos del lado del cliente, por lo que lo usaría para iniciar la cuenta regresiva. ¿Qué otro método estás pensando para inicializar?
dotnetom
No tenía nada más en particular en mente, pero me parecía torpe usar tantos "trozos" dentro de un componente. Supongo que solo soy yo el que me está acostumbrando a cómo funcionan las partes y piezas en React. ¡Una vez más, gracias!
José
4
No hay una necesidad real de almacenar el valor de setInterval como parte del estado porque no afecta la representación
Gil
32

Cuenta atrás actualizada de 10 segundos usando class Clock extends Component

import React, { Component } from 'react';

class Clock extends Component {
  constructor(props){
    super(props);
    this.state = {currentCount: 10}
  }
  timer() {
    this.setState({
      currentCount: this.state.currentCount - 1
    })
    if(this.state.currentCount < 1) { 
      clearInterval(this.intervalId);
    }
  }
  componentDidMount() {
    this.intervalId = setInterval(this.timer.bind(this), 1000);
  }
  componentWillUnmount(){
    clearInterval(this.intervalId);
  }
  render() {
    return(
      <div>{this.state.currentCount}</div>
    );
  }
}

module.exports = Clock;
Greg Herbowicz
fuente
20

Cuenta atrás actualizada de 10 segundos usando Hooks (una nueva propuesta de características que te permite usar el estado y otras características de React sin escribir una clase. Actualmente están en React v16.7.0-alpha).

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

const Clock = () => {
    const [currentCount, setCount] = useState(10);
    const timer = () => setCount(currentCount - 1);

    useEffect(
        () => {
            if (currentCount <= 0) {
                return;
            }
            const id = setInterval(timer, 1000);
            return () => clearInterval(id);
        },
        [currentCount]
    );

    return <div>{currentCount}</div>;
};

const App = () => <Clock />;

ReactDOM.render(<App />, document.getElementById('root'));
Greg Herbowicz
fuente
Con React 16.8, React Hooks están disponibles en una versión estable.
Greg Herbowicz
2

Gracias @dotnetom, @ greg-herbowicz

Si devuelve "this.state is undefined" - función de temporizador de enlace:

constructor(props){
    super(props);
    this.state = {currentCount: 10}
    this.timer = this.timer.bind(this)
}
tulsluper
fuente
2

Si alguien está buscando un enfoque de React Hook para implementar setInterval. Dan Abramov habló de ello en su blog . Compruébalo si quieres una buena lectura sobre el tema, incluido un enfoque de clase. Básicamente, el código es un Hook personalizado que convierte setInterval en declarativo.

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

También publicando el enlace CodeSandbox para mayor comodidad: https://codesandbox.io/s/105x531vkq

Jo E.
fuente
0

Actualizando el estado cada segundo en la clase de reacción. Tenga en cuenta que my index.js pasa una función que devuelve la hora actual.

import React from "react";

class App extends React.Component {
  constructor(props){
    super(props)

    this.state = {
      time: this.props.time,

    }        
  }
  updateMe() {
    setInterval(()=>{this.setState({time:this.state.time})},1000)        
  }
  render(){
  return (
    <div className="container">
      <h1>{this.state.time()}</h1>
      <button onClick={() => this.updateMe()}>Get Time</button>
    </div>
  );
}
}
export default App;
Ashok Shah
fuente