Over the last few months the creators of React have been updating their docs with ES6 compatible code. They've also updated their tutorial, which includes suggestions for a few extra improvements. This article describes how I made one of those improvements.
Rewrite Board to use two loops to make the squares instead of hardcoding them.
We can rewrite the board using nested loops. We'll use one loop to create three individual squares and another one to take these squares and wrap them in a div. We'll do this last bit three times...giving us our 3x3 grid!
Since we'll be using a loop to create components, we need to add a key property so React can tell them apart. We'll need to add a key to both the individual squares and the rows. For the row, we can use the loop index as the key, the squares can use their unique grid number (0, 1, 2 etc). In order to add a key to individual squares, we'll wrap them in a span.
Remember, the key only has to be unique to sibling components. It's fine if some squares and row components share the same key.
See the Pen React Tutorial - Q3 by Alvin Pascoe (@alvinpascoe) on CodePen.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
function Square(props){
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
let boardSquares = [];
for(let row = 0; row < 3; row++){
let boardRow = [];
for(let col = 0; col < 3; col++){
boardRow.push(<span key={(row * 3) + col}>{this.renderSquare((row * 3) + col)}</span>);
}
boardSquares.push(<div className="board-row" key={row}>{boardRow}</div>);
}
return (
<div>
{boardSquares}
</div>
);
}
}
class Game extends React.Component {
constructor() {
super();
this.state = {
history: [{
squares: Array(9).fill(null),
clickedSquare: [0,0],
}],
stepNumber: 0,
xIsNext: true,
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares,
clickedSquare:[Math.floor((i % 3) + 1), Math.floor((i / 3) + 1)]
}]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
});
}
render() {
const active = {
fontWeight: 'bold'
};
const inactive = {
fontWeight: 'normal'
};
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const clickedSquare = step.clickedSquare;
const desc = move ?
`Move # ${move} - (${clickedSquare[0]},${clickedSquare[1]})`:
`Game start`;
return (
<li key={move}>
<a href="#" style={this.state.stepNumber === move ? active : inactive} onClick={() => this.jumpTo(move)}>{desc}</a>
</li>
);
});
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}