Permalink
Browse files

Stop passing around tile component

The logic around flipping tiles and marking them as correct was
previously contained in methods that the Board called on the Tile
component. While somewhat neat, this is not idiomatic react. Instead,
the Tile class should get most of what it needs to render in its props,
and all the logic should be contained in the Board class. In general,
passing components around, calling methods on them, and accessing
another component’s props are all anti-patterns.
  • Loading branch information...
ianobermiller committed Aug 30, 2014
1 parent 072431f commit eb99289124a0154edfcd59486afc45e47bfefa77
Showing with 72 additions and 54 deletions.
  1. +56 −22 src/board.jsx
  2. +4 −4 src/game.jsx
  3. +12 −28 src/tile.jsx
View
@@ -2,48 +2,69 @@
var Board = React.createClass({
propTypes: {
- onGameFinished: React.PropTypes.func.isRequired,
max: React.PropTypes.number.isRequired,
- tiles: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
+ onGameFinished: React.PropTypes.func.isRequired,
+ words: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
},
getInitialState() {
return {
+ correctIndexes: [],
+ firstFlipIndex: null,
found: 0,
+ isWaiting: false,
message: 'choosetile',
+ wrongIndexes: [],
};
},
- onTileClicked(tile){
- if (this.wait) {
+ onTileClicked(index) {
+ if (this.state.isWaiting) {
return;
}
+ var correctIndexes = this.state.correctIndexes;
+ var firstFlipIndex = this.state.firstFlipIndex;
+ var words = this.props.words;
+
// turn up lone tile
- if (!this.flippedtile){
- this.flippedtile = tile;
- tile.reveal();
- this.setState({message: 'findmate'});
+ if (firstFlipIndex === null){
+ this.setState({
+ firstFlipIndex: index,
+ message: 'findmate'
+ });
return;
}
// clicked second
- this.wait = true;
- if (this.flippedtile.props.word === tile.props.word){
- this.setState({found: this.state.found + 1, message: 'foundmate'});
- tile.succeed();
- this.flippedtile.succeed();
+ if (words[index] === words[firstFlipIndex]) {
+ this.setState({
+ correctIndexes: correctIndexes.concat([index, firstFlipIndex]),
+ firstFlipIndex: null,
+ found: this.state.found + 1,
+ isWaiting: true,
+ message: 'foundmate',
+ });
} else {
- this.setState({message: 'wrong'});
- tile.fail();
- this.flippedtile.fail();
+ this.setState({
+ firstFlipIndex: null,
+ isWaiting: true,
+ message: 'wrong',
+ wrongIndexes: [index, firstFlipIndex],
+ });
}
setTimeout(
() => {
- this.wait = false;
- this.setState({message: 'choosetile'});
- delete this.flippedtile;
+ if (!this.isMounted()) {
+ return;
+ }
+
+ this.setState({
+ isWaiting: false,
+ message: 'choosetile',
+ wrongIndexes: [],
+ });
},
2000
);
@@ -60,9 +81,22 @@ var Board = React.createClass({
max={this.props.max}
message={this.state.message}
/>
- {this.props.tiles.map(
- (b, n) => <Tile word={b} key={n} onClick={this.onTileClicked} />
- )}
+ {this.props.words.map((word, index) => {
+ var isFirstFlip = index === this.state.firstFlipIndex;
+ var isCorrect = _.contains(this.state.correctIndexes, index);
+ var isWrong = _.contains(this.state.wrongIndexes, index);
+ return (
+ <Tile
+ word={word}
+ key={index}
+ index={index}
+ isFlipped={isFirstFlip || isCorrect || isWrong}
+ isCorrect={isCorrect}
+ isWrong={isWrong}
+ onClick={this.onTileClicked}
+ />
+ );
+ })}
</div>
);
}
View
@@ -2,12 +2,12 @@
var Game = React.createClass({
getInitialState() {
- return {playing: false, tiles:[]};
+ return {playing: false, words: []};
},
startGame(words) {
this.setState({
- tiles: _.shuffle(words.concat(words)),
+ words: _.shuffle(words.concat(words)),
playing: true
});
},
@@ -20,8 +20,8 @@ var Game = React.createClass({
return this.state.playing ? (
<Board
onGameFinished={this.reset}
- tiles={this.state.tiles}
- max={this.state.tiles.length / 2}
+ words={this.state.words}
+ max={this.state.words.length / 2}
/>
) : (
<Wordform onWordsEntered={this.startGame} />
View
@@ -4,46 +4,30 @@ var cx = React.addons.classSet;
var Tile = React.createClass({
propTypes: {
- word: React.PropTypes.string.isRequired,
+ index: React.PropTypes.number.isRequired,
+ isCorrect: React.PropTypes.bool.isRequired,
+ isFlipped: React.PropTypes.bool.isRequired,
+ isWrong: React.PropTypes.bool.isRequired,
onClick: React.PropTypes.func.isRequired,
+ word: React.PropTypes.string.isRequired,
},
- getInitialState() {
- return {flipped: false};
- },
-
- catchClick() {
- if (!this.state.flipped) {
- this.props.onClick(this);
+ onClick() {
+ if (!this.props.isFlipped) {
+ this.props.onClick(this.props.index);
}
},
- reveal() {
- this.setState({flipped: true});
- },
-
- fail() {
- this.setState({flipped: true, wrong: true});
- setTimeout(
- () => this.setState({flipped: false, wrong: false}),
- 2000
- );
- },
-
- succeed() {
- this.setState({flipped: true, correct: true});
- },
-
render() {
return (
<div
className={cx({
'brick': true,
- 'flipped': this.state.flipped,
- 'correct': this.state.correct,
- 'wrong': this.state.wrong,
+ 'flipped': this.props.isFlipped,
+ 'correct': this.props.isCorrect,
+ 'wrong': this.props.isWrong,
})}
- onClick={this.catchClick}>
+ onClick={this.onClick}>
<div className="front">?</div>
<div className="back">{this.props.word}</div>
</div>

0 comments on commit eb99289

Please sign in to comment.