setInterval em um aplicativo React

101

Ainda sou bastante novo no React, mas tenho trabalhado lentamente e encontrei algo em que estou preso.

Estou tentando construir um componente "cronômetro" no React e, para ser honesto, não sei se estou fazendo isso da maneira certa (ou eficiente). No meu código abaixo, eu definir o estado para retornar um objeto { currentCount: 10 }e foram brincar com componentDidMount, componentWillUnmounte, rendere eu só pode obter o estado a "contagem regressiva" 10-9.

Pergunta de duas partes: O que estou entendendo de errado? E há uma maneira mais eficiente de usar setTimeout (em vez de usar componentDidMount& componentWillUnmount)?

Agradeço antecipadamente.

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
fonte
2
bind(this)não é mais necessário, o Reagir faz isso sozinho agora.
Derek Pollard
2
seu método de cronômetro não atualiza currentCount
Bryan Chen
1
@Derek tem certeza? Acabei de fazer o meu funcionar adicionando this.timer.bind(this)como this.timer sozinho não funcionou
O worm
6
@Theworm @Derek está errado, mais ou menos. React.createClass (que está obsoleto) vincula automaticamente os métodos, mas class Clock extends Componentnão liga automaticamente. Portanto, depende de como você está criando seus componentes se você precisa ligá-los.
CallMeNorm

Respostas:

157

Vejo 4 problemas com seu código:

  • No método do cronômetro, você sempre define sua contagem atual para 10
  • Você tenta atualizar o estado no método de renderização
  • Você não usa setStatemétodo para realmente alterar o estado
  • Você não está armazenando o seu intervalId no estado

Vamos tentar consertar isso:

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>
    );
}

Isso resultaria em um cronômetro que diminuiria de 10 para -N. Se você quiser que o cronômetro diminua para 0, pode usar a versão ligeiramente modificada:

timer: function() {
   var newCount = this.state.currentCount - 1;
   if(newCount >= 0) { 
       this.setState({ currentCount: newCount });
   } else {
       clearInterval(this.state.intervalId);
   }
},
dotnetom
fonte
Obrigado. Isso faz muitosentido. Ainda sou um iniciante e estou tentando entender como funciona o estado e o que acontece em quais "pedaços", como renderização.
Jose
Eu estou me perguntando, porém, é necessário usar componentDidMount e componentWillUnmount para realmente definir o intervalo? EDIT: Acabei de ver sua edição mais recente. :)
Jose
@Jose eu acho que componentDidMounté o lugar certo para acionar os eventos do lado do cliente, então eu o usaria para iniciar a contagem regressiva. Que outro método você está pensando para inicializar?
dotnetom
Eu não tinha mais nada em particular em mente, mas parecia desajeitado usar tantos "pedaços" dentro de um componente. Suponho que seja apenas eu me acostumando a como os bits e peças funcionam no React. Mais uma vez obrigado!
Jose
4
Não há necessidade real de armazenar o valor setInterval como parte do estado porque ele não afeta a renderização
Gil
32

Contagem regressiva de 10 segundos atualizada 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
fonte
20

Contagem regressiva de 10 segundos atualizada usando Hooks (uma nova proposta de recurso que permite usar o estado e outros recursos do React sem escrever uma classe. Eles estão atualmente no 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
fonte
Com o React 16.8, os React Hooks estão disponíveis em uma versão estável.
Greg Herbowicz
2

Obrigado @dotnetom, @ greg-herbowicz

Se retornar "this.state is undefined" - função de temporizador de ligação:

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

Se alguém estiver procurando por uma abordagem React Hook para implementar setInterval. Dan Abramov falou sobre isso em seu blog . Confira se quiser uma boa leitura sobre o assunto, incluindo uma abordagem de classe. Basicamente, o código é um Gancho personalizado que torna setInterval 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]);
}

Também postando o link CodeSandbox para sua conveniência: https://codesandbox.io/s/105x531vkq

Jo E.
fonte
0

Atualizando o estado a cada segundo na classe react. Observe que o my index.js passa uma função que retorna a hora atual.

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
fonte