Skip to content

Commit eb99289

Browse files
committed
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.
1 parent 072431f commit eb99289

File tree

3 files changed

+72
-54
lines changed

3 files changed

+72
-54
lines changed

src/board.jsx

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,69 @@
22

33
var Board = React.createClass({
44
propTypes: {
5-
onGameFinished: React.PropTypes.func.isRequired,
65
max: React.PropTypes.number.isRequired,
7-
tiles: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
6+
onGameFinished: React.PropTypes.func.isRequired,
7+
words: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
88
},
99

1010
getInitialState() {
1111
return {
12+
correctIndexes: [],
13+
firstFlipIndex: null,
1214
found: 0,
15+
isWaiting: false,
1316
message: 'choosetile',
17+
wrongIndexes: [],
1418
};
1519
},
1620

17-
onTileClicked(tile){
18-
if (this.wait) {
21+
onTileClicked(index) {
22+
if (this.state.isWaiting) {
1923
return;
2024
}
2125

26+
var correctIndexes = this.state.correctIndexes;
27+
var firstFlipIndex = this.state.firstFlipIndex;
28+
var words = this.props.words;
29+
2230
// turn up lone tile
23-
if (!this.flippedtile){
24-
this.flippedtile = tile;
25-
tile.reveal();
26-
this.setState({message: 'findmate'});
31+
if (firstFlipIndex === null){
32+
this.setState({
33+
firstFlipIndex: index,
34+
message: 'findmate'
35+
});
2736
return;
2837
}
2938

3039
// clicked second
31-
this.wait = true;
32-
if (this.flippedtile.props.word === tile.props.word){
33-
this.setState({found: this.state.found + 1, message: 'foundmate'});
34-
tile.succeed();
35-
this.flippedtile.succeed();
40+
if (words[index] === words[firstFlipIndex]) {
41+
this.setState({
42+
correctIndexes: correctIndexes.concat([index, firstFlipIndex]),
43+
firstFlipIndex: null,
44+
found: this.state.found + 1,
45+
isWaiting: true,
46+
message: 'foundmate',
47+
});
3648
} else {
37-
this.setState({message: 'wrong'});
38-
tile.fail();
39-
this.flippedtile.fail();
49+
this.setState({
50+
firstFlipIndex: null,
51+
isWaiting: true,
52+
message: 'wrong',
53+
wrongIndexes: [index, firstFlipIndex],
54+
});
4055
}
4156

4257
setTimeout(
4358
() => {
44-
this.wait = false;
45-
this.setState({message: 'choosetile'});
46-
delete this.flippedtile;
59+
if (!this.isMounted()) {
60+
return;
61+
}
62+
63+
this.setState({
64+
isWaiting: false,
65+
message: 'choosetile',
66+
wrongIndexes: [],
67+
});
4768
},
4869
2000
4970
);
@@ -60,9 +81,22 @@ var Board = React.createClass({
6081
max={this.props.max}
6182
message={this.state.message}
6283
/>
63-
{this.props.tiles.map(
64-
(b, n) => <Tile word={b} key={n} onClick={this.onTileClicked} />
65-
)}
84+
{this.props.words.map((word, index) => {
85+
var isFirstFlip = index === this.state.firstFlipIndex;
86+
var isCorrect = _.contains(this.state.correctIndexes, index);
87+
var isWrong = _.contains(this.state.wrongIndexes, index);
88+
return (
89+
<Tile
90+
word={word}
91+
key={index}
92+
index={index}
93+
isFlipped={isFirstFlip || isCorrect || isWrong}
94+
isCorrect={isCorrect}
95+
isWrong={isWrong}
96+
onClick={this.onTileClicked}
97+
/>
98+
);
99+
})}
66100
</div>
67101
);
68102
}

src/game.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
var Game = React.createClass({
44
getInitialState() {
5-
return {playing: false, tiles:[]};
5+
return {playing: false, words: []};
66
},
77

88
startGame(words) {
99
this.setState({
10-
tiles: _.shuffle(words.concat(words)),
10+
words: _.shuffle(words.concat(words)),
1111
playing: true
1212
});
1313
},
@@ -20,8 +20,8 @@ var Game = React.createClass({
2020
return this.state.playing ? (
2121
<Board
2222
onGameFinished={this.reset}
23-
tiles={this.state.tiles}
24-
max={this.state.tiles.length / 2}
23+
words={this.state.words}
24+
max={this.state.words.length / 2}
2525
/>
2626
) : (
2727
<Wordform onWordsEntered={this.startGame} />

src/tile.jsx

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,30 @@ var cx = React.addons.classSet;
44

55
var Tile = React.createClass({
66
propTypes: {
7-
word: React.PropTypes.string.isRequired,
7+
index: React.PropTypes.number.isRequired,
8+
isCorrect: React.PropTypes.bool.isRequired,
9+
isFlipped: React.PropTypes.bool.isRequired,
10+
isWrong: React.PropTypes.bool.isRequired,
811
onClick: React.PropTypes.func.isRequired,
12+
word: React.PropTypes.string.isRequired,
913
},
1014

11-
getInitialState() {
12-
return {flipped: false};
13-
},
14-
15-
catchClick() {
16-
if (!this.state.flipped) {
17-
this.props.onClick(this);
15+
onClick() {
16+
if (!this.props.isFlipped) {
17+
this.props.onClick(this.props.index);
1818
}
1919
},
2020

21-
reveal() {
22-
this.setState({flipped: true});
23-
},
24-
25-
fail() {
26-
this.setState({flipped: true, wrong: true});
27-
setTimeout(
28-
() => this.setState({flipped: false, wrong: false}),
29-
2000
30-
);
31-
},
32-
33-
succeed() {
34-
this.setState({flipped: true, correct: true});
35-
},
36-
3721
render() {
3822
return (
3923
<div
4024
className={cx({
4125
'brick': true,
42-
'flipped': this.state.flipped,
43-
'correct': this.state.correct,
44-
'wrong': this.state.wrong,
26+
'flipped': this.props.isFlipped,
27+
'correct': this.props.isCorrect,
28+
'wrong': this.props.isWrong,
4529
})}
46-
onClick={this.catchClick}>
30+
onClick={this.onClick}>
4731
<div className="front">?</div>
4832
<div className="back">{this.props.word}</div>
4933
</div>

0 commit comments

Comments
 (0)