Created
September 18, 2016 06:07
-
-
Save MutableLoss/b6ec104c823f105dcc03d68b823e4042 to your computer and use it in GitHub Desktop.
Treehouse React Basics in ES6
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// even though we change this data, the array of objects | |
// will always be exactly that | |
const players = [ | |
{ key: 1, name: 'D', score: 21 }, | |
{ key: 2, name: 'B', score: 23 }, | |
{ key: 3, name: 'P', score: 13 } | |
]; | |
class Stopwatch extends React.Component { | |
constructor() { | |
super(); | |
this.onStart = this.onStart.bind(this); | |
this.onStop = this.onStop.bind(this); | |
this.onReset = this.onReset.bind(this); | |
this.onTick = this.onTick.bind(this); | |
this.state = { | |
running: false, | |
elapsedTime: 0, | |
previousTime: 0 | |
} | |
} | |
componentDidMount() { | |
this.interval = setInterval(this.onTick, 100); | |
} | |
componentWillUnmount() { | |
clearInterval(this.interval); | |
} | |
onTick() { | |
if (this.state.running) { | |
var now = Date.now(); | |
this.setState({ | |
previousTime: now, | |
elapsedTime: this.state.elapsedTime + (now - this.state.previousTime) | |
}); | |
} | |
console.log('tick'); | |
} | |
onStart() { | |
this.setState({ | |
running: true, | |
previousTime: Date.now() | |
}); | |
} | |
onStop() { | |
this.setState({running: false}); | |
} | |
onReset() { | |
this.setState({ | |
elapsedTime: 0, | |
previousTime: Date.now() | |
}); | |
} | |
render() { | |
return ( | |
<div className="stopwatch"> | |
<h2>Stopwatch</h2> | |
<div className="stopwatch-time">{Math.floor(this.state.elapsedTime / 1000)}</div> | |
{this.state.running ? | |
<button onClick={this.onStop}>Stop</button> : | |
<button onClick={this.onStart}>Start</button>} | |
<button onClick={this.onReset}>Reset</button> | |
</div> | |
) | |
} | |
} | |
// since this is not transpiled code, we need to put all child | |
// components above the root component. Start at the bottom if | |
// you are just now opening this file. | |
class AddPlayer extends React.Component { | |
constructor() { | |
super(); | |
this.onNameChange = this.onNameChange.bind(this); | |
this.onSubmit = this.onSubmit.bind(this); | |
this.state = { | |
name: '' | |
} | |
} | |
onSubmit(e) { | |
e.preventDefault(); | |
this.props.onAdd(this.state.name); | |
this.setState({name: ''}); | |
} | |
onNameChange(e) { | |
this.setState({name: e.target.value}); | |
} | |
render() { | |
return ( | |
<div className="add-player-form"> | |
<form onSubmit={this.onSubmit}> | |
<input type="text" value={this.state.name} onChange={this.onNameChange} /> | |
<input type="submit" value="Add Player" /> | |
</form> | |
</div> | |
) | |
} | |
} | |
const Stats = (props) => { | |
var totalPlayers = props.players.length; | |
var totalPoints = props.players.reduce((total, player) => { | |
return total + player.score; | |
}, 0); | |
return ( | |
<div> | |
<table className="stats"> | |
<tbody> | |
<tr> | |
<td>Players:</td> | |
<td>{totalPlayers}</td> | |
</tr> | |
<tr> | |
<td>Total Points:</td> | |
<td>{totalPoints}</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
) | |
} | |
Stats.propTypes = { | |
players: React.PropTypes.array.isRequired | |
} | |
// this is a great way to model out a stateless component | |
// sure we could take in props, but we can be explicit as well | |
// allowing these to be reused for a specific list of props | |
// being passed. Think 'functional'. ^_^ | |
// we could also use ...props if rest worked here | |
const Header = ({players, title}) => { | |
return ( | |
<div className="header"> | |
<Stats players={players} /> | |
<h1>{title}</h1> | |
<Stopwatch /> | |
</div> | |
); | |
} | |
Header.propTypes = { | |
title: React.PropTypes.string.isRequired, | |
players: React.PropTypes.array.isRequired | |
} | |
// showing distinct input of props as exact variables | |
const Counter = ({score, onChange}) => { | |
return ( | |
<div className="counter"> | |
<button | |
className="counter-action decrement" onClick={() => {onChange(-1)}}> - </button> | |
<div className="counter-score">{score}</div> | |
<button | |
className="counter-action increment" onClick={() => {onChange(1)}}> + </button> | |
</div> | |
) | |
} | |
Counter.propTypes = { | |
score: React.PropTypes.number.isRequired, | |
onChange: React.PropTypes.func.isRequired | |
} | |
const Player = ({name, score, scoreOnChange, onRemove}) => { | |
return ( | |
<div className="players"> | |
<div className="player"> | |
<div className="player-name"> | |
<a className="remove-player" onClick={onRemove}>X</a> | |
{name} | |
</div> | |
<div className="player-score"> | |
<Counter score={score} onChange={scoreOnChange} /> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
Player.propTypes = { | |
name: React.PropTypes.string.isRequired, | |
score: React.PropTypes.number.isRequired, | |
scoreOnChange: React.PropTypes.func.isRequired, | |
onRemove: React.PropTypes.func.isRequired | |
}; | |
class Application extends React.Component { | |
constructor(state, props){ | |
super(state, props); | |
// remove the need for a bind step when calling. We could use fat arrow | |
// declared variable functions that auto-bind, but they are not supported | |
// in workspaces. | |
// e.g.: const scoreOnChange = (index, num) => { /*rest of method*/ } | |
this.scoreOnChange = this.scoreOnChange.bind(this); | |
this.onPlayerAdd = this.onPlayerAdd.bind(this); | |
// since ES6 doesn't need or use getInitialState/Props, we use a constructor | |
this.state = { | |
title: 'ES6 Scoreboard', | |
players: this.props.somePlayers, | |
nextId: players.length + 1 | |
} | |
} | |
// look at these beautifully easy to write methods! | |
scoreOnChange(index, num) { | |
this.state.players[index].score += num; | |
this.setState(this.state); | |
} | |
onPlayerAdd(name) { | |
this.state.players.push({ | |
name: name, | |
score: 0, | |
key: this.state.nextId | |
}); | |
this.setState(this.state); | |
// we don't want nextId's jumping all over the place | |
// so we make sure the next one IS the next id | |
this.setState({nextId: this.state.players.length + 1}); | |
} | |
onRemove(index) { | |
this.state.players.splice(index, 1); | |
// we recalc the nextId after removing too | |
this.setState({nextId: this.state.players.length + 1}); | |
} | |
render() { | |
return ( | |
<div className="scoreboard"> | |
<Header title="ES6 Scoreboard" players={this.state.players} /> | |
{this.state.players.map((player, index) => { | |
return ( | |
<Player | |
key={player.key} | |
name={player.name} | |
score={player.score} | |
scoreOnChange={this.scoreOnChange.bind(this, index)} | |
onRemove={this.onRemove.bind(this, index)} | |
/> | |
); | |
})} | |
<AddPlayer onAdd={this.onPlayerAdd} /> | |
</div> | |
) | |
} | |
} | |
Application.propTypes = { | |
title: React.PropTypes.string, | |
somePlayers: React.PropTypes.arrayOf(React.PropTypes.shape({ | |
name: React.PropTypes.string.isRequired, | |
score: React.PropTypes.number.isRequired | |
})).isRequired | |
} | |
ReactDOM.render(<Application somePlayers={players} />, document.getElementById('container')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment