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.
Add a toggle button that lets you sort the moves in either ascending or descending order.
In order to toggle the move sort direction, we need to create a variable to track whether we're currently sorting in ascending or descending order. Adding another property to the Game component's state works well for this (I used ascending).
Next, we need to add a simple button to our output (responsible for toggling the sort order) and create an onClick method. The handler for the onClick method will simply inverse the state property used to track order.
Once sort order is being correctly tracked and updated, we can rewrite the code that displays the moves to check the ascending property and based on the result, display the moves in chronological or reverse order.
See the Pen React Tutorial - Q4 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,
ascending: 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,
});
}
sortHandleClick(){
this.setState({
ascending: !this.state.ascending
});
}
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 ascending = this.state.ascending;
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>{ascending ? moves : moves.reverse()}</ol>
<button onClick={() => this.sortHandleClick()}>Toggle Sort Order</button>
</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;
}