Skip to content
aviral gupta

// B1.1 · ~30 min · Beginner

Components and JSX

After this lesson you write React components that return JSX, nest them into a tic-tac-toe board, and show JavaScript values with curly braces.

Lesson 1 of 5 in B1 Quick start

Start of the module

You will be able to

  • Write a component as a capitalised function that returns JSX, and nest it in another component
  • Apply the JSX rules: one root element or a Fragment <>…</>, closed tags, className
  • Show data with curly braces, and export default the component the runner renders
  1. Warm-up · Activity 1 of 7

    Warm-up from the JavaScript course: a function, an object and a template literal. What does this print?

    const user = { name: 'Ada', role: 'engineer' };
    
    function intro(person) {
      return `${person.name} is an ${person.role}`;
    }
    
    console.log(intro(user));
  2. Predict · Activity 2 of 7

    Predict before you read on. This is a React component: a function that returns JSX. What text does it show on the page?

    function Sum() {
      return <p>2 + 2 = {2 + 2}</p>;
    }
  3. Practice · Activity 3 of 7

    Fill in the attribute that gives the button the CSS class square in JSX, without a warning from React.

    <button ____="square">X</button>
    <button ="square">X</button>
  4. Practice · Activity 4 of 7

    Match each piece of JSX to what it does.

  5. Practice · Activity 5 of 7

    How many <button> elements does Board put on the page?

    function Square() {
      return <button className="square">X</button>;
    }
    
    function Board() {
      return (
        <>
          <div className="board-row">
            <Square />
            <Square />
            <Square />
          </div>
          <div className="board-row">
            <Square />
            <Square />
            <Square />
          </div>
        </>
      );
    }
  6. Brain teaser · Activity 6 of 7

    Brain teaser. Someone named the component in lowercase. How many buttons appear?

    function square() {
      return <button className="square">X</button>;
    }
    
    function Board() {
      return (
        <div className="board-row">
          <square />
          <square />
        </div>
      );
    }
  7. Apply · Activity 7 of 7

    Mini-task: a page for a tic-tac-toe club. In main.jsx, put the club's name and founding year in an object site. Write a Header component that shows the name in an <h1> with the class title, and a Footer that shows the name and the year in a <p>, both with curly braces. The default export, App, returns a Fragment with Header, one paragraph of your own and Footer. Run it and check that the page shows no warning.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A tic-tac-toe board from Square components

The tutorial on react.dev builds its board from small pieces, and so does this example. Square returns one button. Board, the default export, nests nine squares in three rows and puts a heading and a status line above them, both read from the object game with curly braces. A Fragment is the single root, so the rows sit directly in #root. styles.css gives the squares their size; the runner applies .css files to the page by itself.

main.jsx

const game = {
  title: 'Tic-tac-toe',
  nextPlayer: 'X'
};

function Square() {
  return <button className="square">{game.nextPlayer}</button>;
}

export default function Board() {
  return (
    <>
      <h1>{game.title}</h1>
      <p className="status">Next player: {game.nextPlayer}</p>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

styles.css

.board-row {
  display: flex;
}

.square {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 -1px -1px 0;
  border: 1px solid #999;
  background: #fff;
  font-size: 1.5rem;
  font-weight: bold;
}

Run it with

npm run dev

Output

<!-- #root -->
<div id="root"><h1>Tic-tac-toe</h1><p class="status">Next player: X</p><div class="board-row"><button class="square">X</button><button class="square">X</button><button class="square">X</button></div><div class="board-row"><button class="square">X</button><button class="square">X</button><button class="square">X</button></div><div class="board-row"><button class="square">X</button><button class="square">X</button><button class="square">X</button></div></div>
  • The Fragment leaves no element of its own: the heading, the status line and the rows are direct children of #root.
  • className="square" became class="square" in the page.
  • Each {game.nextPlayer} shows X; lesson B1.2 gives every square its own value with props.
  • Nothing was printed before the snapshot: the component bodies contain no console.log.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads React and the compiler that turns JSX into JavaScript (up to 2 MB) and keeps them cached. Your components run in your browser and stay on your device.

Your page

Your page runs in an isolated frame. It has no access to this site, and its localStorage is kept in memory for this run only: the next run starts empty.

Run the program to see its page here.

Exercises

Exercise 1 of 2

Nine squares in three rows

Build the board from the tutorial. Board should render nine buttons with the class square, showing 1 to 9 in order, in three <div> elements with the class board-row, three buttons in each. Return the three rows in a Fragment, so they sit directly in #root. The starter renders one square.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads React and the compiler that turns JSX into JavaScript (up to 2 MB) and keeps them cached. Your components run in your browser and stay on your device.

Your page

Your page runs in an isolated frame. It has no access to this site, and its localStorage is kept in memory for this run only: the next run starts empty.

Run the program to see its page here.

Hints
  1. Hint 1

    Copying a second <button> next to the first gives error TS2657: the component needs one root.

  2. Hint 2

    Wrap everything in <>…</>, and put three <div className="board-row"> inside it.

  3. Hint 3

    Each row holds three <button className="square"> elements: 1 2 3, then 4 5 6, then 7 8 9.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export default function Board() {
  return (
    <>
      <div className="board-row">
        <button className="square">1</button>
        <button className="square">2</button>
        <button className="square">3</button>
      </div>
      <div className="board-row">
        <button className="square">4</button>
        <button className="square">5</button>
        <button className="square">6</button>
      </div>
      <div className="board-row">
        <button className="square">7</button>
        <button className="square">8</button>
        <button className="square">9</button>
      </div>
    </>
  );
}
Run it on your computer

Install React 19.3 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.jsx

export default function Board() {
  // TODO: three rows of three squares, numbered 1 to 9.
  return <button className="square">1</button>;
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, within} from '@testing-library/react';
import Board from './main.jsx';

test('The board shows nine squares numbered 1 to 9', () => {
  render(<Board />);
  const labels = screen.getAllByRole('button').map((button) => button.textContent);
  assert.deepEqual(labels, ['1', '2', '3', '4', '5', '6', '7', '8', '9'], 'the squares read ' + JSON.stringify(labels));
});

test('The squares sit in three rows of three', () => {
  const {container} = render(<Board />);
  const rows = [...container.children];
  assert.equal(rows.length, 3, 'the board puts ' + rows.length + ' element(s) into #root, not 3 rows');
  rows.forEach((row, i) => {
    const count = within(row).queryAllByRole('button').length;
    assert.equal(count, 3, 'row ' + (i + 1) + ' holds ' + count + ' square(s)');
  });
});

test('Rows and squares have their CSS classes', () => {
  const {container} = render(<Board />);
  const rows = container.querySelectorAll('div.board-row').length;
  const squares = container.querySelectorAll('button.square').length;
  assert.equal(rows, 3, rows + ' element(s) have the class board-row');
  assert.equal(squares, 9, squares + ' button(s) have the class square');
});

test('React prints no warnings', () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    render(<Board />);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error while rendering');
});

styles.css

.board-row {
  display: flex;
}

.square {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 -1px -1px 0;
  border: 1px solid #999;
  background: #fff;
  font-size: 1.5rem;
  font-weight: bold;
}

In a Vite React project (npm create vite@latest, template react), put main.jsx in src/ as App.jsx and start it with npm run dev. React renders it inside <StrictMode>, as here.

Run the program:

npm run dev

There is no command for the checks on your computer yet. They are in main.test.jsx, written with node:test and React Testing Library.

Exercise 2 of 2

A profile card

Profile shows the object user. Avatar renders an <img> with the class avatar, its src read from user.imageUrl, and the alt text "Photo of " followed by user.name. Profile shows user.name in the <h2> and user.role in the <p>. Use curly braces for every value; type no name or address twice. The picture does not load in the runner, so you will see its alt text; the tests check the attributes.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads React and the compiler that turns JSX into JavaScript (up to 2 MB) and keeps them cached. Your components run in your browser and stay on your device.

Your page

Your page runs in an isolated frame. It has no access to this site, and its localStorage is kept in memory for this run only: the next run starts empty.

Run the program to see its page here.

Hints
  1. Hint 1

    Run it first: React warns about class, and the picture's src is the text user.imageUrl.

  2. Hint 2

    Use className, and write src={user.imageUrl} with braces instead of quotes.

  3. Hint 3

    For the alt text, join two strings in braces: alt={'Photo of ' + user.name}, or use a template literal.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

const user = {
  name: 'Hedy Lamarr',
  role: 'Inventor and actor',
  imageUrl: 'hedy-lamarr.jpg'
};

function Avatar() {
  return <img className="avatar" src={user.imageUrl} alt={'Photo of ' + user.name} />;
}

export default function Profile() {
  return (
    <article className="profile">
      <Avatar />
      <h2>{user.name}</h2>
      <p>{user.role}</p>
    </article>
  );
}
Run it on your computer

Install React 19.3 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.jsx

const user = {
  name: 'Hedy Lamarr',
  role: 'Inventor and actor',
  imageUrl: 'hedy-lamarr.jpg'
};

function Avatar() {
  // TODO: use className, read the address from user.imageUrl, and build the alt text from user.name.
  return <img class="avatar" src="user.imageUrl" alt="Photo" />;
}

export default function Profile() {
  // TODO: show the name and the role from user.
  return (
    <article className="profile">
      <Avatar />
      <h2>Name</h2>
      <p>Role</p>
    </article>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import Profile from './main.jsx';

test('The heading shows the name from user', () => {
  render(<Profile />);
  const heading = screen.getByRole('heading', {level: 2}).textContent;
  assert.equal(heading, 'Hedy Lamarr', 'the heading reads ' + JSON.stringify(heading));
});

test('The role from user is shown', () => {
  render(<Profile />);
  assert.ok(screen.queryByText('Inventor and actor'), 'no element shows the text Inventor and actor');
});

test('The picture reads its address from user.imageUrl', () => {
  render(<Profile />);
  const src = screen.getByRole('img').getAttribute('src');
  assert.equal(src, 'hedy-lamarr.jpg', 'the picture has src=' + JSON.stringify(src));
});

test('The alt text is Photo of Hedy Lamarr', () => {
  render(<Profile />);
  const alt = screen.getByRole('img').getAttribute('alt');
  assert.equal(alt, 'Photo of Hedy Lamarr', 'the picture has alt=' + JSON.stringify(alt));
});

test('The picture has the CSS class avatar', () => {
  render(<Profile />);
  const className = screen.getByRole('img').className;
  assert.equal(className, 'avatar', 'the picture has the class ' + JSON.stringify(className));
});

test('React prints no warnings', () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    render(<Profile />);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error while rendering');
});

styles.css

.profile {
  font-family: sans-serif;
}

.avatar {
  width: 90px;
  height: 90px;
  border-radius: 50%;
}

In a Vite React project (npm create vite@latest, template react), put main.jsx in src/ as App.jsx and start it with npm run dev. React renders it inside <StrictMode>, as here.

Run the program:

npm run dev

There is no command for the checks on your computer yet. They are in main.test.jsx, written with node:test and React Testing Library.

Common mistakes

Returning two elements side by side

export default function Board() {
  return (
    <h1>Tic-tac-toe</h1>
    <div className="board-row">
      <button className="square">1</button>
    </div>
  );
}

What React prints

error TS2657: JSX expressions must have one parent element.

Why, and the fix

A component returns one root element, so nothing runs: the JSX transform stops with this error. Wrap the siblings in a parent, such as a <div>, or in a Fragment, <>…</>, which adds no element to the page. The runner's transform is TypeScript's; react.dev's sandbox words the same mistake as "Adjacent JSX elements must be wrapped in an enclosing tag."

A component with a lowercase name

function square() {
  return <button className="square">X</button>;
}

export default function Board() {
  return (
    <div className="board-row">
      <square />
      <square />
    </div>
  );
}

What React prints

The tag <square> is unrecognized in this browser.

Why, and the fix

JSX treats a lowercase tag as HTML, so <square /> creates an unknown <square> element and never calls your function. The warning goes on: "If you meant to render a React component, start its name with an uppercase letter." Rename the function Square and write <Square />.

Writing class instead of className

export default function Square() {
  return <button class="square">X</button>;
}

What React prints

Invalid DOM property `class`. Did you mean `className`?

Why, and the fix

In JSX the CSS class is className. React 19 still puts class="square" on the button, so the page may even look right, but it warns in the console, and the course runner counts any warning as a failed run. Write <button className="square">.

React in the browser: React 19.3.0 (development build) and React Testing Library, MIT, served from this site; JSX is compiled by TypeScript 6.0.3, Apache-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

Components are functions that return JSX

A React component is a JavaScript function that returns markup, such as function Square() { return <button className="square">X</button>; }. The markup is JSX: it looks like HTML but sits inside JavaScript. Once Square exists, another component uses it like a tag, <Square />, as often as it likes: a Board can hold nine of them. The name must start with a capital letter. That is how JSX tells your component from an HTML tag: <Square /> calls your function, while <square /> would be an unknown HTML element, and React warns about it in the console.

JSX is stricter than HTML

Three rules catch most beginners. First, a component returns one root element. Two siblings side by side, such as an <h1> next to a <div>, do not compile; wrap them in a parent, such as a <div>, or in a Fragment, <>…</>, which groups them without adding an element to the page. Second, every tag is closed: <br /> and <img … /> end with a slash, and <button> needs its </button>. Third, the CSS class is written className, because class is a reserved word in JavaScript. React still renders class, but warns about it.

Curly braces and export default

Curly braces switch from JSX back to JavaScript. <h1>{game.title}</h1> shows the value of game.title, and any expression works: {2 + 2}, {user.score * 2}, or a template literal. In an attribute, use braces instead of quotes: src={user.imageUrl} passes the value, while src="user.imageUrl" passes the words user.imageUrl as plain text. Finally, export default marks the main component of a file. The course runner renders the default export of main.jsx into the page's empty #root, inside React's Strict Mode, just as a Vite project renders App.

Sources

Last reviewed October 5, 2026