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".

src/main.jsx
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:

src/main.jsx
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:

src/main.jsx
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:

src/main.jsx (complete)
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!