React board game app
$begingroup$
I was told to code a board game app using React for a job interview.
They told me they chose other candidates to move forward with, so I was wondering on what I could improve.
The full code can be viewed here.
Here are the more interesting components:
TicTacToeGame
import React, {useCallback} from 'react'; // useCallback imported by mistake here..
import './tic-tac-toe.scss';
import update from 'immutability-helper';
import Board from '@/board/board';
import ScoreList from '@/score-list/score-list';
import {isGameOver, isDraw} from '../../utils/tic-tac-toe';
import x from '#/images/x.svg';
import o from '#/images/o.svg';
import restart from '#/images/restart.svg';
const X = 0;
const O = 1;
const EMPTY = 2;
const xImg = <img className="xo" src={x}/>;
const oImg = <img className="xo" src={o}/>;
const IMAGE_MAP = {
[X]: xImg,
[O]: oImg
};
const INITIAL_STATE = {
turn: 0,
players: [{
score: 0
}, {
score: 0
}],
board: [
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]
]
};
class TicTacToe extends React.Component {
constructor(props) {
super(props);
this.state = INITIAL_STATE;
}
renderBox = (row, column) => {
const {board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
return null;
}
return IMAGE_MAP[value];
};
boxClickHandler = (row, column) => {
const {turn, players, board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
const newBoard = update(board, {[row]: {$splice: [[column, 1, turn % 2]]}});
if (isGameOver(newBoard, row, column)) {
// React will batch both setStates since it's in onClick event handler
const newPlayers = update(players, {
[turn % 2]: {
score: {
$apply: function (s) {
return s + 1;
}
}
}
});
this.setState({
turn: INITIAL_STATE.turn,
players: newPlayers,
board: INITIAL_STATE.board
});
} else if (isDraw(board, turn)) {
this.setState({
turn: INITIAL_STATE.turn,
players,
board: INITIAL_STATE.board
});
}
else {
this.setState({
turn: turn + 1,
board: newBoard
});
}
}
};
resetGame = () => {
this.setState({
...INITIAL_STATE
});
};
render() {
const {players, board} = this.state;
return <div className="tic-tac-toe">
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<h1 className="title">
Tic - Tac - Toe
</h1>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<Board
board={board}
width={400}
height={400}
isGameOver={isGameOver}
boxTemplate={this.renderBox}
onBoxClick={this.boxClickHandler}/>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<div className="scores-and-reset">
<ScoreList
width={340}
height={100}
players={players}/>
<button className="reset-button" onClick={this.resetGame}>
Reset
<img src={restart}/>
</button>
</div>
</div>
</div>
</div>;
}
}
export default TicTacToe;
Board
import React, {useCallback} from 'react';
import './board.scss';
import Box from '@/box/box';
import classNames from 'classnames';
const Board = ({board, width, height, boxTemplate, onBoxClick}) => {
return <div className="board" style={{width, height}}>
{board.map((row, i) => <div key={i} className="row" style={{height: height / board.length}}>
{row.map((column, j) => {
const boxClass = classNames({
top: i !== 0,
right: j !== row.length - 1,
bottom: i !== board.length - 1,
left: j !== 0
});
const memoizedBoxClickCallback = useCallback(
(e) => {
onBoxClick(i, j);
},
[i, j],
);
return <Box key={i + j} className={boxClass} width={width / row.length} height={height / board.length} onClick={memoizedBoxClickCallback}>
{boxTemplate(i, j)}
</Box>;
})}
</div>
)}
</div>;
};
export default Board;
Box
import React from 'react';
import './box.scss';
class Box extends React.PureComponent {
render() {
const {children, className, width, height, onClick} = this.props;
return <div className={`box ${className} pointer`} style={{width, height}} onClick={onClick}>
{children}
</div>;
}
}
export default Box;
Design choices:
I tried to make the base components reusable for other implementation of board games so I pass
onClickhandler for theBoxcomponent andboxTemplatefunction as a children to theBoxcomponent to render whatever the game wants (e.g TicTacToeGame will render XO respectively, SnakesNLaddersGames will render snakes and ladders respectively and so on..), also theBoardcomponent sets classtoprightleftbottomrespectively (depends on where the box is relative to the edge of the board) without styles for eachBoxso the game component it-self could decide on the styling of these components based on the location of theBox.I didn't use any state management library as this is a pretty small project. I know adding such library could benefit me in the future for when the application expands, but right now I don't see a reason for the state logic update (which is inside the function
boxClickHandlerinTicTacToeGame) to be inside a reducer function (even if usinguseReducerhook) because this logic is not a reusable logic for other board games that might get implemented using the baseBoardandBoxcomponents.
The only thing I didn't took into account is that the functional component Board might get rendered for nothing if a game component state which doesn't require board update will change, but since I implemented only TicTacTacGame and all its state requires re-render of the Board component I didn't took this into account. If I were to solve this I would just use PureComponenet or React.memo on the component.
interview-questions tic-tac-toe react.js jsx sass
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
add a comment |
$begingroup$
I was told to code a board game app using React for a job interview.
They told me they chose other candidates to move forward with, so I was wondering on what I could improve.
The full code can be viewed here.
Here are the more interesting components:
TicTacToeGame
import React, {useCallback} from 'react'; // useCallback imported by mistake here..
import './tic-tac-toe.scss';
import update from 'immutability-helper';
import Board from '@/board/board';
import ScoreList from '@/score-list/score-list';
import {isGameOver, isDraw} from '../../utils/tic-tac-toe';
import x from '#/images/x.svg';
import o from '#/images/o.svg';
import restart from '#/images/restart.svg';
const X = 0;
const O = 1;
const EMPTY = 2;
const xImg = <img className="xo" src={x}/>;
const oImg = <img className="xo" src={o}/>;
const IMAGE_MAP = {
[X]: xImg,
[O]: oImg
};
const INITIAL_STATE = {
turn: 0,
players: [{
score: 0
}, {
score: 0
}],
board: [
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]
]
};
class TicTacToe extends React.Component {
constructor(props) {
super(props);
this.state = INITIAL_STATE;
}
renderBox = (row, column) => {
const {board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
return null;
}
return IMAGE_MAP[value];
};
boxClickHandler = (row, column) => {
const {turn, players, board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
const newBoard = update(board, {[row]: {$splice: [[column, 1, turn % 2]]}});
if (isGameOver(newBoard, row, column)) {
// React will batch both setStates since it's in onClick event handler
const newPlayers = update(players, {
[turn % 2]: {
score: {
$apply: function (s) {
return s + 1;
}
}
}
});
this.setState({
turn: INITIAL_STATE.turn,
players: newPlayers,
board: INITIAL_STATE.board
});
} else if (isDraw(board, turn)) {
this.setState({
turn: INITIAL_STATE.turn,
players,
board: INITIAL_STATE.board
});
}
else {
this.setState({
turn: turn + 1,
board: newBoard
});
}
}
};
resetGame = () => {
this.setState({
...INITIAL_STATE
});
};
render() {
const {players, board} = this.state;
return <div className="tic-tac-toe">
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<h1 className="title">
Tic - Tac - Toe
</h1>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<Board
board={board}
width={400}
height={400}
isGameOver={isGameOver}
boxTemplate={this.renderBox}
onBoxClick={this.boxClickHandler}/>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<div className="scores-and-reset">
<ScoreList
width={340}
height={100}
players={players}/>
<button className="reset-button" onClick={this.resetGame}>
Reset
<img src={restart}/>
</button>
</div>
</div>
</div>
</div>;
}
}
export default TicTacToe;
Board
import React, {useCallback} from 'react';
import './board.scss';
import Box from '@/box/box';
import classNames from 'classnames';
const Board = ({board, width, height, boxTemplate, onBoxClick}) => {
return <div className="board" style={{width, height}}>
{board.map((row, i) => <div key={i} className="row" style={{height: height / board.length}}>
{row.map((column, j) => {
const boxClass = classNames({
top: i !== 0,
right: j !== row.length - 1,
bottom: i !== board.length - 1,
left: j !== 0
});
const memoizedBoxClickCallback = useCallback(
(e) => {
onBoxClick(i, j);
},
[i, j],
);
return <Box key={i + j} className={boxClass} width={width / row.length} height={height / board.length} onClick={memoizedBoxClickCallback}>
{boxTemplate(i, j)}
</Box>;
})}
</div>
)}
</div>;
};
export default Board;
Box
import React from 'react';
import './box.scss';
class Box extends React.PureComponent {
render() {
const {children, className, width, height, onClick} = this.props;
return <div className={`box ${className} pointer`} style={{width, height}} onClick={onClick}>
{children}
</div>;
}
}
export default Box;
Design choices:
I tried to make the base components reusable for other implementation of board games so I pass
onClickhandler for theBoxcomponent andboxTemplatefunction as a children to theBoxcomponent to render whatever the game wants (e.g TicTacToeGame will render XO respectively, SnakesNLaddersGames will render snakes and ladders respectively and so on..), also theBoardcomponent sets classtoprightleftbottomrespectively (depends on where the box is relative to the edge of the board) without styles for eachBoxso the game component it-self could decide on the styling of these components based on the location of theBox.I didn't use any state management library as this is a pretty small project. I know adding such library could benefit me in the future for when the application expands, but right now I don't see a reason for the state logic update (which is inside the function
boxClickHandlerinTicTacToeGame) to be inside a reducer function (even if usinguseReducerhook) because this logic is not a reusable logic for other board games that might get implemented using the baseBoardandBoxcomponents.
The only thing I didn't took into account is that the functional component Board might get rendered for nothing if a game component state which doesn't require board update will change, but since I implemented only TicTacTacGame and all its state requires re-render of the Board component I didn't took this into account. If I were to solve this I would just use PureComponenet or React.memo on the component.
interview-questions tic-tac-toe react.js jsx sass
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
add a comment |
$begingroup$
I was told to code a board game app using React for a job interview.
They told me they chose other candidates to move forward with, so I was wondering on what I could improve.
The full code can be viewed here.
Here are the more interesting components:
TicTacToeGame
import React, {useCallback} from 'react'; // useCallback imported by mistake here..
import './tic-tac-toe.scss';
import update from 'immutability-helper';
import Board from '@/board/board';
import ScoreList from '@/score-list/score-list';
import {isGameOver, isDraw} from '../../utils/tic-tac-toe';
import x from '#/images/x.svg';
import o from '#/images/o.svg';
import restart from '#/images/restart.svg';
const X = 0;
const O = 1;
const EMPTY = 2;
const xImg = <img className="xo" src={x}/>;
const oImg = <img className="xo" src={o}/>;
const IMAGE_MAP = {
[X]: xImg,
[O]: oImg
};
const INITIAL_STATE = {
turn: 0,
players: [{
score: 0
}, {
score: 0
}],
board: [
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]
]
};
class TicTacToe extends React.Component {
constructor(props) {
super(props);
this.state = INITIAL_STATE;
}
renderBox = (row, column) => {
const {board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
return null;
}
return IMAGE_MAP[value];
};
boxClickHandler = (row, column) => {
const {turn, players, board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
const newBoard = update(board, {[row]: {$splice: [[column, 1, turn % 2]]}});
if (isGameOver(newBoard, row, column)) {
// React will batch both setStates since it's in onClick event handler
const newPlayers = update(players, {
[turn % 2]: {
score: {
$apply: function (s) {
return s + 1;
}
}
}
});
this.setState({
turn: INITIAL_STATE.turn,
players: newPlayers,
board: INITIAL_STATE.board
});
} else if (isDraw(board, turn)) {
this.setState({
turn: INITIAL_STATE.turn,
players,
board: INITIAL_STATE.board
});
}
else {
this.setState({
turn: turn + 1,
board: newBoard
});
}
}
};
resetGame = () => {
this.setState({
...INITIAL_STATE
});
};
render() {
const {players, board} = this.state;
return <div className="tic-tac-toe">
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<h1 className="title">
Tic - Tac - Toe
</h1>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<Board
board={board}
width={400}
height={400}
isGameOver={isGameOver}
boxTemplate={this.renderBox}
onBoxClick={this.boxClickHandler}/>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<div className="scores-and-reset">
<ScoreList
width={340}
height={100}
players={players}/>
<button className="reset-button" onClick={this.resetGame}>
Reset
<img src={restart}/>
</button>
</div>
</div>
</div>
</div>;
}
}
export default TicTacToe;
Board
import React, {useCallback} from 'react';
import './board.scss';
import Box from '@/box/box';
import classNames from 'classnames';
const Board = ({board, width, height, boxTemplate, onBoxClick}) => {
return <div className="board" style={{width, height}}>
{board.map((row, i) => <div key={i} className="row" style={{height: height / board.length}}>
{row.map((column, j) => {
const boxClass = classNames({
top: i !== 0,
right: j !== row.length - 1,
bottom: i !== board.length - 1,
left: j !== 0
});
const memoizedBoxClickCallback = useCallback(
(e) => {
onBoxClick(i, j);
},
[i, j],
);
return <Box key={i + j} className={boxClass} width={width / row.length} height={height / board.length} onClick={memoizedBoxClickCallback}>
{boxTemplate(i, j)}
</Box>;
})}
</div>
)}
</div>;
};
export default Board;
Box
import React from 'react';
import './box.scss';
class Box extends React.PureComponent {
render() {
const {children, className, width, height, onClick} = this.props;
return <div className={`box ${className} pointer`} style={{width, height}} onClick={onClick}>
{children}
</div>;
}
}
export default Box;
Design choices:
I tried to make the base components reusable for other implementation of board games so I pass
onClickhandler for theBoxcomponent andboxTemplatefunction as a children to theBoxcomponent to render whatever the game wants (e.g TicTacToeGame will render XO respectively, SnakesNLaddersGames will render snakes and ladders respectively and so on..), also theBoardcomponent sets classtoprightleftbottomrespectively (depends on where the box is relative to the edge of the board) without styles for eachBoxso the game component it-self could decide on the styling of these components based on the location of theBox.I didn't use any state management library as this is a pretty small project. I know adding such library could benefit me in the future for when the application expands, but right now I don't see a reason for the state logic update (which is inside the function
boxClickHandlerinTicTacToeGame) to be inside a reducer function (even if usinguseReducerhook) because this logic is not a reusable logic for other board games that might get implemented using the baseBoardandBoxcomponents.
The only thing I didn't took into account is that the functional component Board might get rendered for nothing if a game component state which doesn't require board update will change, but since I implemented only TicTacTacGame and all its state requires re-render of the Board component I didn't took this into account. If I were to solve this I would just use PureComponenet or React.memo on the component.
interview-questions tic-tac-toe react.js jsx sass
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
I was told to code a board game app using React for a job interview.
They told me they chose other candidates to move forward with, so I was wondering on what I could improve.
The full code can be viewed here.
Here are the more interesting components:
TicTacToeGame
import React, {useCallback} from 'react'; // useCallback imported by mistake here..
import './tic-tac-toe.scss';
import update from 'immutability-helper';
import Board from '@/board/board';
import ScoreList from '@/score-list/score-list';
import {isGameOver, isDraw} from '../../utils/tic-tac-toe';
import x from '#/images/x.svg';
import o from '#/images/o.svg';
import restart from '#/images/restart.svg';
const X = 0;
const O = 1;
const EMPTY = 2;
const xImg = <img className="xo" src={x}/>;
const oImg = <img className="xo" src={o}/>;
const IMAGE_MAP = {
[X]: xImg,
[O]: oImg
};
const INITIAL_STATE = {
turn: 0,
players: [{
score: 0
}, {
score: 0
}],
board: [
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]
]
};
class TicTacToe extends React.Component {
constructor(props) {
super(props);
this.state = INITIAL_STATE;
}
renderBox = (row, column) => {
const {board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
return null;
}
return IMAGE_MAP[value];
};
boxClickHandler = (row, column) => {
const {turn, players, board} = this.state;
const value = board[row][column];
if (value === EMPTY) {
const newBoard = update(board, {[row]: {$splice: [[column, 1, turn % 2]]}});
if (isGameOver(newBoard, row, column)) {
// React will batch both setStates since it's in onClick event handler
const newPlayers = update(players, {
[turn % 2]: {
score: {
$apply: function (s) {
return s + 1;
}
}
}
});
this.setState({
turn: INITIAL_STATE.turn,
players: newPlayers,
board: INITIAL_STATE.board
});
} else if (isDraw(board, turn)) {
this.setState({
turn: INITIAL_STATE.turn,
players,
board: INITIAL_STATE.board
});
}
else {
this.setState({
turn: turn + 1,
board: newBoard
});
}
}
};
resetGame = () => {
this.setState({
...INITIAL_STATE
});
};
render() {
const {players, board} = this.state;
return <div className="tic-tac-toe">
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<h1 className="title">
Tic - Tac - Toe
</h1>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<Board
board={board}
width={400}
height={400}
isGameOver={isGameOver}
boxTemplate={this.renderBox}
onBoxClick={this.boxClickHandler}/>
</div>
</div>
<div className="tic-tac-toe-row">
<div className="tic-tac-toe-wrapper">
<div className="scores-and-reset">
<ScoreList
width={340}
height={100}
players={players}/>
<button className="reset-button" onClick={this.resetGame}>
Reset
<img src={restart}/>
</button>
</div>
</div>
</div>
</div>;
}
}
export default TicTacToe;
Board
import React, {useCallback} from 'react';
import './board.scss';
import Box from '@/box/box';
import classNames from 'classnames';
const Board = ({board, width, height, boxTemplate, onBoxClick}) => {
return <div className="board" style={{width, height}}>
{board.map((row, i) => <div key={i} className="row" style={{height: height / board.length}}>
{row.map((column, j) => {
const boxClass = classNames({
top: i !== 0,
right: j !== row.length - 1,
bottom: i !== board.length - 1,
left: j !== 0
});
const memoizedBoxClickCallback = useCallback(
(e) => {
onBoxClick(i, j);
},
[i, j],
);
return <Box key={i + j} className={boxClass} width={width / row.length} height={height / board.length} onClick={memoizedBoxClickCallback}>
{boxTemplate(i, j)}
</Box>;
})}
</div>
)}
</div>;
};
export default Board;
Box
import React from 'react';
import './box.scss';
class Box extends React.PureComponent {
render() {
const {children, className, width, height, onClick} = this.props;
return <div className={`box ${className} pointer`} style={{width, height}} onClick={onClick}>
{children}
</div>;
}
}
export default Box;
Design choices:
I tried to make the base components reusable for other implementation of board games so I pass
onClickhandler for theBoxcomponent andboxTemplatefunction as a children to theBoxcomponent to render whatever the game wants (e.g TicTacToeGame will render XO respectively, SnakesNLaddersGames will render snakes and ladders respectively and so on..), also theBoardcomponent sets classtoprightleftbottomrespectively (depends on where the box is relative to the edge of the board) without styles for eachBoxso the game component it-self could decide on the styling of these components based on the location of theBox.I didn't use any state management library as this is a pretty small project. I know adding such library could benefit me in the future for when the application expands, but right now I don't see a reason for the state logic update (which is inside the function
boxClickHandlerinTicTacToeGame) to be inside a reducer function (even if usinguseReducerhook) because this logic is not a reusable logic for other board games that might get implemented using the baseBoardandBoxcomponents.
The only thing I didn't took into account is that the functional component Board might get rendered for nothing if a game component state which doesn't require board update will change, but since I implemented only TicTacTacGame and all its state requires re-render of the Board component I didn't took this into account. If I were to solve this I would just use PureComponenet or React.memo on the component.
interview-questions tic-tac-toe react.js jsx sass
interview-questions tic-tac-toe react.js jsx sass
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
edited 7 hours ago
Jorayen
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
asked 7 hours ago
JorayenJorayen
1114
1114
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
Jorayen is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
Overall, it looks pretty good to me, pretty much all my comments are opinionated to a lesser or (often) greater extent. I'd summarize my main feedback as "YAGNI" and "think readability, not reusability".
Less Reusability
Unless it was stipulated in the interview, I think the concern of trying to make this reusable was unnecessary and somewhat unhelpful. IMO, premature reusability is a common source of unnecessary complication (like premature optimization).
Certainly, there are some obvious cases for reusability (like UI widgets), but trying to make core "business concerns" like a game implementation reusable is, in my experience, often more trouble than it's worth. In general, YAGNI. And when you guess about how something may be reused in the future you often build the wrong abstraction.
Specifically, I think the Board and Box are complicated by trying to be more flexible than they really need to be.
More CSS
The CSS code wasn't included, but you should probably leverage CSS more.
Setting explicit widths and heights in the view is inflexible and could probably be handled more gracefully in CSS - depending on browser support requirements, CSS Grid might be a good fit, but more 'traditional' approaches would work too. (Even if you went with fixed width/height, it'd still be better to have that in CSS than your HTML)
Similar with
left,right,top,bottom: which I believe are better handled with:first-childand:last-childselectors.
State Management
I agree with your decision to not use a state management library, as I do think that would be overkill. I do, however, find the large amount of logic in boxClickHandler unsatisfying - it's not wrong, but I don't think it's very good readability to have the entire tic-tac-toe logic in a click handler: some separation of the view and the logic would be good.
I do think useReducer would be a good fit: personally I'd probably define the reducer function in its own file - it's good for readability from a separation of concerns perspective, and if you were going to write unit tests it'd be much better for that. It's trivial to write good unit tests for a reducer function - much harder when that logic is in a click handler.
(Though even just defining a makeMove function on the TicTacToe component and calling it from the click handler would have been something of an improvement, IMO)
Nits
- Maybe split things into more components: the TicTacToe component has fairly large render function: personally, I like my top-level components to look more like:
render() {
return (
<>
<Title />
<Game /*...*/ />
<Scores /*...*/ />
</>
)
}
(Even if those components are just defined elsewhere in the same file, I like when a component's render function makes the overall structure obvious)
useCallbackis technically violating the rules of hooks: it's not at the top of the function as hooks are supposed to be: they're not supposed to be in 'loops' like.map. In practice, the current version works: since there's always the same number of boxes, the call order is consistent, but I'd advise against breaking the hook rules, anyways.
I'd probably share a single click handler among the boxes and either have the boxes pass in their (i, j) coordinate when they call the click handler, or the coordinates could be attached to the element via data-attributes and read by the click handler.
Alternatively, you can save complexity by not using useCallback or PureComponent - both Dan Abramov and sophiebits (core React developers) have said that you shouldn't use PureComputed everywhere unless you've measured performance first.
$endgroup$
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Jorayen is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216352%2freact-board-game-app%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Overall, it looks pretty good to me, pretty much all my comments are opinionated to a lesser or (often) greater extent. I'd summarize my main feedback as "YAGNI" and "think readability, not reusability".
Less Reusability
Unless it was stipulated in the interview, I think the concern of trying to make this reusable was unnecessary and somewhat unhelpful. IMO, premature reusability is a common source of unnecessary complication (like premature optimization).
Certainly, there are some obvious cases for reusability (like UI widgets), but trying to make core "business concerns" like a game implementation reusable is, in my experience, often more trouble than it's worth. In general, YAGNI. And when you guess about how something may be reused in the future you often build the wrong abstraction.
Specifically, I think the Board and Box are complicated by trying to be more flexible than they really need to be.
More CSS
The CSS code wasn't included, but you should probably leverage CSS more.
Setting explicit widths and heights in the view is inflexible and could probably be handled more gracefully in CSS - depending on browser support requirements, CSS Grid might be a good fit, but more 'traditional' approaches would work too. (Even if you went with fixed width/height, it'd still be better to have that in CSS than your HTML)
Similar with
left,right,top,bottom: which I believe are better handled with:first-childand:last-childselectors.
State Management
I agree with your decision to not use a state management library, as I do think that would be overkill. I do, however, find the large amount of logic in boxClickHandler unsatisfying - it's not wrong, but I don't think it's very good readability to have the entire tic-tac-toe logic in a click handler: some separation of the view and the logic would be good.
I do think useReducer would be a good fit: personally I'd probably define the reducer function in its own file - it's good for readability from a separation of concerns perspective, and if you were going to write unit tests it'd be much better for that. It's trivial to write good unit tests for a reducer function - much harder when that logic is in a click handler.
(Though even just defining a makeMove function on the TicTacToe component and calling it from the click handler would have been something of an improvement, IMO)
Nits
- Maybe split things into more components: the TicTacToe component has fairly large render function: personally, I like my top-level components to look more like:
render() {
return (
<>
<Title />
<Game /*...*/ />
<Scores /*...*/ />
</>
)
}
(Even if those components are just defined elsewhere in the same file, I like when a component's render function makes the overall structure obvious)
useCallbackis technically violating the rules of hooks: it's not at the top of the function as hooks are supposed to be: they're not supposed to be in 'loops' like.map. In practice, the current version works: since there's always the same number of boxes, the call order is consistent, but I'd advise against breaking the hook rules, anyways.
I'd probably share a single click handler among the boxes and either have the boxes pass in their (i, j) coordinate when they call the click handler, or the coordinates could be attached to the element via data-attributes and read by the click handler.
Alternatively, you can save complexity by not using useCallback or PureComponent - both Dan Abramov and sophiebits (core React developers) have said that you shouldn't use PureComputed everywhere unless you've measured performance first.
$endgroup$
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
add a comment |
$begingroup$
Overall, it looks pretty good to me, pretty much all my comments are opinionated to a lesser or (often) greater extent. I'd summarize my main feedback as "YAGNI" and "think readability, not reusability".
Less Reusability
Unless it was stipulated in the interview, I think the concern of trying to make this reusable was unnecessary and somewhat unhelpful. IMO, premature reusability is a common source of unnecessary complication (like premature optimization).
Certainly, there are some obvious cases for reusability (like UI widgets), but trying to make core "business concerns" like a game implementation reusable is, in my experience, often more trouble than it's worth. In general, YAGNI. And when you guess about how something may be reused in the future you often build the wrong abstraction.
Specifically, I think the Board and Box are complicated by trying to be more flexible than they really need to be.
More CSS
The CSS code wasn't included, but you should probably leverage CSS more.
Setting explicit widths and heights in the view is inflexible and could probably be handled more gracefully in CSS - depending on browser support requirements, CSS Grid might be a good fit, but more 'traditional' approaches would work too. (Even if you went with fixed width/height, it'd still be better to have that in CSS than your HTML)
Similar with
left,right,top,bottom: which I believe are better handled with:first-childand:last-childselectors.
State Management
I agree with your decision to not use a state management library, as I do think that would be overkill. I do, however, find the large amount of logic in boxClickHandler unsatisfying - it's not wrong, but I don't think it's very good readability to have the entire tic-tac-toe logic in a click handler: some separation of the view and the logic would be good.
I do think useReducer would be a good fit: personally I'd probably define the reducer function in its own file - it's good for readability from a separation of concerns perspective, and if you were going to write unit tests it'd be much better for that. It's trivial to write good unit tests for a reducer function - much harder when that logic is in a click handler.
(Though even just defining a makeMove function on the TicTacToe component and calling it from the click handler would have been something of an improvement, IMO)
Nits
- Maybe split things into more components: the TicTacToe component has fairly large render function: personally, I like my top-level components to look more like:
render() {
return (
<>
<Title />
<Game /*...*/ />
<Scores /*...*/ />
</>
)
}
(Even if those components are just defined elsewhere in the same file, I like when a component's render function makes the overall structure obvious)
useCallbackis technically violating the rules of hooks: it's not at the top of the function as hooks are supposed to be: they're not supposed to be in 'loops' like.map. In practice, the current version works: since there's always the same number of boxes, the call order is consistent, but I'd advise against breaking the hook rules, anyways.
I'd probably share a single click handler among the boxes and either have the boxes pass in their (i, j) coordinate when they call the click handler, or the coordinates could be attached to the element via data-attributes and read by the click handler.
Alternatively, you can save complexity by not using useCallback or PureComponent - both Dan Abramov and sophiebits (core React developers) have said that you shouldn't use PureComputed everywhere unless you've measured performance first.
$endgroup$
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
add a comment |
$begingroup$
Overall, it looks pretty good to me, pretty much all my comments are opinionated to a lesser or (often) greater extent. I'd summarize my main feedback as "YAGNI" and "think readability, not reusability".
Less Reusability
Unless it was stipulated in the interview, I think the concern of trying to make this reusable was unnecessary and somewhat unhelpful. IMO, premature reusability is a common source of unnecessary complication (like premature optimization).
Certainly, there are some obvious cases for reusability (like UI widgets), but trying to make core "business concerns" like a game implementation reusable is, in my experience, often more trouble than it's worth. In general, YAGNI. And when you guess about how something may be reused in the future you often build the wrong abstraction.
Specifically, I think the Board and Box are complicated by trying to be more flexible than they really need to be.
More CSS
The CSS code wasn't included, but you should probably leverage CSS more.
Setting explicit widths and heights in the view is inflexible and could probably be handled more gracefully in CSS - depending on browser support requirements, CSS Grid might be a good fit, but more 'traditional' approaches would work too. (Even if you went with fixed width/height, it'd still be better to have that in CSS than your HTML)
Similar with
left,right,top,bottom: which I believe are better handled with:first-childand:last-childselectors.
State Management
I agree with your decision to not use a state management library, as I do think that would be overkill. I do, however, find the large amount of logic in boxClickHandler unsatisfying - it's not wrong, but I don't think it's very good readability to have the entire tic-tac-toe logic in a click handler: some separation of the view and the logic would be good.
I do think useReducer would be a good fit: personally I'd probably define the reducer function in its own file - it's good for readability from a separation of concerns perspective, and if you were going to write unit tests it'd be much better for that. It's trivial to write good unit tests for a reducer function - much harder when that logic is in a click handler.
(Though even just defining a makeMove function on the TicTacToe component and calling it from the click handler would have been something of an improvement, IMO)
Nits
- Maybe split things into more components: the TicTacToe component has fairly large render function: personally, I like my top-level components to look more like:
render() {
return (
<>
<Title />
<Game /*...*/ />
<Scores /*...*/ />
</>
)
}
(Even if those components are just defined elsewhere in the same file, I like when a component's render function makes the overall structure obvious)
useCallbackis technically violating the rules of hooks: it's not at the top of the function as hooks are supposed to be: they're not supposed to be in 'loops' like.map. In practice, the current version works: since there's always the same number of boxes, the call order is consistent, but I'd advise against breaking the hook rules, anyways.
I'd probably share a single click handler among the boxes and either have the boxes pass in their (i, j) coordinate when they call the click handler, or the coordinates could be attached to the element via data-attributes and read by the click handler.
Alternatively, you can save complexity by not using useCallback or PureComponent - both Dan Abramov and sophiebits (core React developers) have said that you shouldn't use PureComputed everywhere unless you've measured performance first.
$endgroup$
Overall, it looks pretty good to me, pretty much all my comments are opinionated to a lesser or (often) greater extent. I'd summarize my main feedback as "YAGNI" and "think readability, not reusability".
Less Reusability
Unless it was stipulated in the interview, I think the concern of trying to make this reusable was unnecessary and somewhat unhelpful. IMO, premature reusability is a common source of unnecessary complication (like premature optimization).
Certainly, there are some obvious cases for reusability (like UI widgets), but trying to make core "business concerns" like a game implementation reusable is, in my experience, often more trouble than it's worth. In general, YAGNI. And when you guess about how something may be reused in the future you often build the wrong abstraction.
Specifically, I think the Board and Box are complicated by trying to be more flexible than they really need to be.
More CSS
The CSS code wasn't included, but you should probably leverage CSS more.
Setting explicit widths and heights in the view is inflexible and could probably be handled more gracefully in CSS - depending on browser support requirements, CSS Grid might be a good fit, but more 'traditional' approaches would work too. (Even if you went with fixed width/height, it'd still be better to have that in CSS than your HTML)
Similar with
left,right,top,bottom: which I believe are better handled with:first-childand:last-childselectors.
State Management
I agree with your decision to not use a state management library, as I do think that would be overkill. I do, however, find the large amount of logic in boxClickHandler unsatisfying - it's not wrong, but I don't think it's very good readability to have the entire tic-tac-toe logic in a click handler: some separation of the view and the logic would be good.
I do think useReducer would be a good fit: personally I'd probably define the reducer function in its own file - it's good for readability from a separation of concerns perspective, and if you were going to write unit tests it'd be much better for that. It's trivial to write good unit tests for a reducer function - much harder when that logic is in a click handler.
(Though even just defining a makeMove function on the TicTacToe component and calling it from the click handler would have been something of an improvement, IMO)
Nits
- Maybe split things into more components: the TicTacToe component has fairly large render function: personally, I like my top-level components to look more like:
render() {
return (
<>
<Title />
<Game /*...*/ />
<Scores /*...*/ />
</>
)
}
(Even if those components are just defined elsewhere in the same file, I like when a component's render function makes the overall structure obvious)
useCallbackis technically violating the rules of hooks: it's not at the top of the function as hooks are supposed to be: they're not supposed to be in 'loops' like.map. In practice, the current version works: since there's always the same number of boxes, the call order is consistent, but I'd advise against breaking the hook rules, anyways.
I'd probably share a single click handler among the boxes and either have the boxes pass in their (i, j) coordinate when they call the click handler, or the coordinates could be attached to the element via data-attributes and read by the click handler.
Alternatively, you can save complexity by not using useCallback or PureComponent - both Dan Abramov and sophiebits (core React developers) have said that you shouldn't use PureComputed everywhere unless you've measured performance first.
answered 3 hours ago
RetsamRetsam
40635
40635
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
add a comment |
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
$begingroup$
Thanks, I appreciate the feedback!
$endgroup$
– Jorayen
2 hours ago
add a comment |
Jorayen is a new contributor. Be nice, and check out our Code of Conduct.
Jorayen is a new contributor. Be nice, and check out our Code of Conduct.
Jorayen is a new contributor. Be nice, and check out our Code of Conduct.
Jorayen is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216352%2freact-board-game-app%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown