React Stopwatch

by Andy Kenward

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.5/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.5/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.0.5/redux.js"></script>
<div id="app"></div>

Babel + JSX

const { createStore } = Redux;

// Initial state for reducer
const initialState = {
  isOn: false,
  time: 0
};

// Reducer function
const timer = (state = initialState, action) => {
  switch (action.type) {
    case 'START_TIMER':
      return {
        ...state,
        isOn: true,
        offset: action.offset
      };
    
    case 'STOP_TIMER':
      return {
        ...initialState
      };
      
    case 'TICK':
      return {
        ...state,
        time: state.time + (action.time - state.offset),
        offset: action.time
      };

    default: 
      return state;
  }
}

// Create store using the reducer
const store = createStore(timer);

// React Component to display the timer
class Timer extends React.Component {
  constructor() {
    super();
    this.start = this.start.bind(this);
    this.stop = this.stop.bind(this);
  }
	
	componentWillUnmount() {
		if (this._interval) cancelAnimationFrame(this._interval);
	}

  start() {
		this._interval = requestAnimationFrame(this.progress);
		
    store.dispatch({
      type: 'START_TIMER',
      offset: Date.now()
    });
  }
	
	progress = () => {
		store.dispatch({
			type: 'TICK',
			time: Date.now()
		});
		this._interval = requestAnimationFrame(this.progress);
	}
  
  stop() {
		cancelAnimationFrame(this._interval);
    store.dispatch({
      type: 'STOP_TIMER'
    });
  }

  click() {
    this.props.isOn ? start() : stop();
  }
  
  format(time) {
    const pad = (time, length) => {
      while (time.length < length) {
        time = '0' + time;
      }
      return time;
    }
    
    time = new Date(time);
    let m = pad(time.getMinutes().toString(), 2);
    let s = pad(time.getSeconds().toString(), 2);
    let ms = pad(time.getMilliseconds().toString(), 3);
    
    return `${m} : ${s} . ${ms}`;
  }

  render() {
    return (
      <div>
        <h1>Time: {this.format(this.props.time)}</h1>
        <button onClick={this.props.isOn ? this.stop : this.start}>
          { this.props.isOn ? 'Stop'...