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';
import { mount } from 'what-framework';

function Square({ value, onClick }) {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
}

function Game() {
  return (
    <div className="game">
      <h1>Tic-Tac-Toe</h1>
      <Square value="X" onClick={() => console.log('clicked')} />
    </div>
  );
}

mount(<Game />, '#app');

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';
import { mount } from 'what-framework';

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>
  );
}

function Game() {
  return (
    <div className="game">
      <h1>Tic-Tac-Toe</h1>
      <Board />
    </div>
  );
}

mount(<Game />, '#app');

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 you render a list with .map(), give each item a key so What can match items up across updates. An index like i is the item's position, not its identity, so the compiler cannot use it for keyed reconciliation: it falls back to positional reconciliation and prints a note in your terminal saying so. That is the right answer for a fixed board of 9 squares that never reorders. For a list that can reorder, key on something stable that belongs to the item itself, like key={item.id}.

Checkpoint

Your code should look like this:

src/main.jsx (complete)
import './styles.css';
import { mount } from 'what-framework';

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>
  );
}

function Game() {
  return (
    <div className="game">
      <h1>Tic-Tac-Toe</h1>
      <Board />
    </div>
  );
}

mount(<Game />, '#app');

You have a board with 9 clickable squares. Next, we'll add state to track the game!