Building the Board
Create the 3x3 grid of squares that makes up our tic-tac-toe board.
The Square Component
Let's start by creating a single square. A square is a button that will display either nothing, "X", or "O".
import './styles.css';
function Square({ value, onClick }) {
return (
<button className="square" onClick={onClick}>
{value}
</button>
);
}
export default function Game() {
return (
<div className="game">
<h1>Tic-Tac-Toe</h1>
<Square value="X" onClick={() => console.log('clicked')} />
</div>
);
}
Click the square and check your browser console. You should see "clicked" logged.
Understanding the Square
Let's break down what we wrote:
function Square({ value, onClick }) {
This is a component that receives two props:
value— What to display in the square (null, "X", or "O")onClick— A function to call when the square is clicked
The component returns a button with these props wired up.
The Board Component
Now let's create the full board with 9 squares:
import './styles.css';
function Square({ value, onClick }) {
return (
<button className="square" onClick={onClick}>
{value}
</button>
);
}
function Board() {
return (
<div className="board">
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
<Square value={null} onClick={() => {}} />
</div>
);
}
export default function Game() {
return (
<div className="game">
<h1>Tic-Tac-Toe</h1>
<Board />
</div>
);
}
You should now see a 3x3 grid of empty squares. The CSS we added earlier handles the grid layout.
Rendering with a Loop
Writing 9 Square components is repetitive. Let's use a loop instead:
function Board() {
const squares = Array(9).fill(null);
return (
<div className="board">
{squares.map((value, i) => (
<Square
key={i}
value={value}
onClick={() => console.log(`Square ${i} clicked`)}
/>
))}
</div>
);
}
Now clicking any square logs its index (0-8). This will be important when we track moves.
Why key={i}?
When rendering lists, each item needs a unique key prop. This helps What efficiently update the DOM when the list changes. For now, the index works fine.
Checkpoint
Your code should look like this:
import './styles.css';
function Square({ value, onClick }) {
return (
<button className="square" onClick={onClick}>
{value}
</button>
);
}
function Board() {
const squares = Array(9).fill(null);
return (
<div className="board">
{squares.map((value, i) => (
<Square
key={i}
value={value}
onClick={() => console.log(`Square ${i} clicked`)}
/>
))}
</div>
);
}
export default function Game() {
return (
<div className="game">
<h1>Tic-Tac-Toe</h1>
<Board />
</div>
);
}
You have a board with 9 clickable squares. Next, we'll add state to track the game!