Input change resets timer

时间:2018-03-25 19:29:10

标签: reactjs setstate react-props react-component

Hello guys I started learning react recently and I'm having some issues. I'm trying to make simple react app, one of the components I'm making is stopwatch.
The issue I'm having is that when I start typing in my input for stopwatch the timer resets.

Here is my main component:

import React, { Component } from 'react';
import Clock from '../components/Clock.jsx';
import Stopwatch from '../components/Stopwatch.jsx';
import '../css/App.css';
import { Form, FormControl, Button } from 'react-bootstrap';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            deadline: 'December 25, 2018',
            newDeadline: '',
            timer: 60,
            newTimer: '',
        };
    }

    changeDeadline() {
        this.setState({
            deadline: this.state.newDeadline,
        });
    }

    changeTimer() {
        this.setState({
            timer: this.state.newTimer,
        });
    }

    render() {
        return (
            <div className='app'>
                <div className='app_title'>
                    Countdown to {this.state.deadline}
                </div>
                <Clock
                    deadline = {this.state.deadline}
                />
                <Form inline >
                    <FormControl
                        className="deadline_input"
                        type="text"
                        placeholder="New date"
                        onChange={event => this.setState({newDeadline: event.target.value})}
                        onKeyPress={event => {
                            if (event.key === 'Enter') {
                                event.preventDefault();
                                this.changeDeadline();
                            }
                        }}
                    />
                    <Button onClick={() => this.changeDeadline()} >
                        Submit
                    </Button>
                </Form>

                <div className="stopwatch_title">
                    Stopwatch from {this.state.timer} seconds
                </div>

                <Form inline>
                    <FormControl
                        className="stopwatch_input"
                        type="text"
                        placeholder="Enter time"
                        onChange={event => this.setState({newTimer: event.target.value})}
                        onKeyPress={event => {
                            if (event.key === 'Enter') {
                                event.preventDefault();
                                this.changeTimer();
                            }
                        }}
                    />
                    <Button onClick={() => this.changeTimer()} >
                        Submit
                    </Button>
                </Form>

                <Stopwatch
                    timer = {this.state.timer}
                />

            </div>
        );
    }
}

export default App;

and my stopwatch component:

import React, {Component} from  'react';
import '../css/App.css';
import { Button } from 'react-bootstrap';


class Stopwatch extends Component {
    constructor(props) {
        super(props);
        this.state = {
            stopwatch: props.timer,
        };
        this.decrementer = null;
    }

    componentWillReceiveProps(nextProps) {
        clearInterval(this.decrementer);
        this.timerCountdown(nextProps.timer);
    }

    timerCountdown(newTimer) {

        // First we update our stopwatch with new timer
        this.setState({
            stopwatch: newTimer
        });

    }

    startTimer() {
        // Then we decrement stopwatch by 1 every second
        this.decrementer = setInterval( () => {
            this.setState({
                stopwatch: this.state.stopwatch -1,
            });
        },1000);
    }

    componentDidUpdate() {
        if (this.state.stopwatch < 1) {
            clearInterval(this.decrementer);
            alert('Countdown finished');
        }
    }

    render() {
        return(
            <div>
                <Button onClick={() => this.startTimer()} >
                    Start
                </Button>
                <div className="stopwatch"> {this.state.stopwatch} </div>
            </div>
        );
    }
}

export default Stopwatch;

Here is gif of the problem https://imgur.com/9xqMW96
As you can see my timer resets after I start typing in input. I would like for it to reset only when user presses enter key or uses submit button.

I've tried doing something like this:

  <input value={this.state.newTimer} onChange={evt => this.updateInputValue(evt)}/>

  updateInputValue: function(evt) {
    this.setState({
      newTimer: evt.target.value
    });
  }

but it didn't work for me. You can see code in action here: https://karadjordje.github.io/countdown-stopwatch-react/

2 个答案:

答案 0 :(得分:1)

You are stopping the interval on each new prop that the component receives.
You can either handle the time in a local state or explicitly pass the proper new values from the parent.

I've made a small basic example so you can see the data flow and how each event is responsible for a small portion of the data.
Hope that helps.

class App extends React.Component {

  state = {
    startTime: 5,
    currentTime: 5,
    textInput: ''
  }

  startTimer = () => {
    if (this.interval) {
      clearInterval(this.interval);
    }
    this.interval = setInterval(() => {
      this.setState(prev => {
        if (prev.currentTime === 0) {
          this.stopTimer();
          return { ...prev, currentTime: prev.startTime };
        } else {
          return {
            ...prev,
            currentTime: prev.currentTime - 1
          }
        }
      })
    }, 1000)
  }

  stopTimer = () => {
    clearInterval(this.interval);
  }

  updateInput = ({ target }) => {
    this.setState(prev => ({ textInput: target.value }));
  }

  setStartTime = () => {
    this.stopTimer();
    this.setState(({ textInput }) => ({ startTime: textInput, currentTime: textInput, textInput: '' }));
  }

  render() {
    const { currentTime, textInput } = this.state;
    return (
      <div >
        <div>{currentTime}</div>
        <button onClick={this.startTimer}>Start timer</button>
        <div>
          <input placeholder="Enter start time" value={textInput} onChange={this.updateInput} />
          <button onClick={this.setStartTime}>Set Start time</button>
        </div>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
	<div id="root"></div>

答案 1 :(得分:1)

我已更新了我的代码。

而不是使用componentWillUpdate我使用componentDidUpdate这是我的代码:

componentDidUpdate(prevProps) {
    console.log('componentDidUpdate', this.props, prevProps);
    if (prevProps.timer !== this.props.timer) {
        this.updateTimer(this.props.timer);
        clearInterval(this.decrementer);
    }

    if (this.state.stopwatch < 1) {
        clearInterval(this.decrementer);
        alert('Countdown finished');
    }
}

基本上我只更新定时器,前一个定时器与当前定时器不同。