Skip to content
aviral gupta

// B1.2 · ~30 min · Beginner

Passing props

After this lesson you pass data from a parent component to a child with props, and number the tic-tac-toe board with one reusable Square.

Lesson 2 of 5 in B1 Quick start

You will be able to

  • Pass values to a child as JSX attributes: strings in quotes, numbers and objects in braces
  • Read props in a child by destructuring them: function Square({ value })
  • Reuse one component with different props, with data flowing from parent to child
  1. Warm-up · Activity 1 of 7

    Warm-up from lesson B1.1: components, JSX and curly braces. What text does Row show?

    const game = { player: 'O' };
    
    function Square() {
      return <button className="square">{game.player}</button>;
    }
    
    function Row() {
      return (
        <div className="board-row">
          <Square />
          <Square />
        </div>
      );
    }
  2. Predict · Activity 2 of 7

    Predict before you read on. Row now writes value="…" on each Square, and Square has { value } in its brackets. What text does Row show?

    function Square({ value }) {
      return <button className="square">{value}</button>;
    }
    
    function Row() {
      return (
        <div className="board-row">
          <Square value="1" />
          <Square value="2" />
          <Square value="3" />
        </div>
      );
    }
  3. Practice · Activity 3 of 7

    Fill in the parameter so Square takes the value prop out of its props and shows it.

    function Square(____) {
      return <button className="square">{value}</button>;
    }
    function Square() {
  4. Practice · Activity 4 of 7

    Match each piece of code to what it does.

  5. Practice · Activity 5 of 7

    App passes an object as a prop. What text does it show?

    function Card({ person }) {
      return <p>{person.name} ({person.age})</p>;
    }
    
    function App() {
      return <Card person={{ name: 'Ada', age: 36 }} />;
    }
  6. Brain teaser · Activity 6 of 7

    Brain teaser. Score adds 1 to the points it receives. What text does App show?

    function Score({ points }) {
      return <p>{points + 1}</p>;
    }
    
    function App() {
      return <Score points="2" />;
    }
  7. Apply · Activity 7 of 7

    Mini-task: a score list. In main.jsx, write a Badge component that receives name and score as props and shows them in an <li> with the class badge, like Ada: 3 points. The default export, Scores, renders a <ul> with three Badges, each with a different name and score. Pass the names as strings and the scores as numbers. 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

Nine numbered squares from one Square

This is the board from react.dev's tutorial after "Passing data through props". Square reads one prop, value, and shows it in its button. Board, the parent, renders Square nine times and passes a different value to each: value="1" to value="9". The squares are the same component; only their props differ. styles.css gives the squares their size; the runner applies .css files to the page by itself.

main.jsx

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

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square value="1" />
        <Square value="2" />
        <Square value="3" />
      </div>
      <div className="board-row">
        <Square value="4" />
        <Square value="5" />
        <Square value="6" />
      </div>
      <div className="board-row">
        <Square value="7" />
        <Square value="8" />
        <Square value="9" />
      </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"><div class="board-row"><button class="square">1</button><button class="square">2</button><button class="square">3</button></div><div class="board-row"><button class="square">4</button><button class="square">5</button><button class="square">6</button></div><div class="board-row"><button class="square">7</button><button class="square">8</button><button class="square">9</button></div></div>
  • Each button shows the value its own <Square> tag passed; nothing is shared between the squares.
  • The prop name value does not appear in the HTML: props reach your component, and only what it returns goes into the page.
  • Without the braces, <button className="square">value</button> would show the word value nine times.
  • Lesson B1.3 makes the squares clickable with state.
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

Number the board with props

Every square in the starter shows 1. Change Square so it reads a value prop and shows it, and change Board so it passes value="1" to value="9" to its nine squares, in order. Keep export in front of Square: the tests also render <Square value="7" /> on its own.

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

    Square needs { value } in its brackets: function Square({ value }).

  2. Hint 2

    Show the prop with braces, {value}; without them the button shows the word value.

  3. Hint 3

    In Board, write the prop on each tag: <Square value="1" />, <Square value="2" />, and so on to 9.

Show a solution

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

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

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square value="1" />
        <Square value="2" />
        <Square value="3" />
      </div>
      <div className="board-row">
        <Square value="4" />
        <Square value="5" />
        <Square value="6" />
      </div>
      <div className="board-row">
        <Square value="7" />
        <Square value="8" />
        <Square value="9" />
      </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 function Square() {
  // TODO: read the value prop and show it.
  return <button className="square">1</button>;
}

export default function Board() {
  // TODO: pass value="1" to value="9" to the nine squares.
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, within} from '@testing-library/react';
import Board, {Square} 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('Square shows the value it is given', () => {
  render(<Square value="7" />);
  const label = screen.getByRole('button').textContent;
  assert.equal(label, '7', '<Square value="7" /> shows ' + JSON.stringify(label));
});

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('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 scoreboard with object props

Player receives two props: player, an object with name and mark, and score, a number. Make it show the name, the mark in brackets and the score, like Ada (X): 3 points. In Scoreboard, give the second Player the object { name: 'Grace', mark: 'O' } and the score 5. Keep export in front of Player: the tests also render it with other props.

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

    Player already destructures its props; read player.name, player.mark and score inside braces.

  2. Hint 2

    Write the text around the braces as it is: {player.name} ({player.mark}): {score} points.

  3. Hint 3

    For Grace, copy Ada's tag and change it: player={{ name: 'Grace', mark: 'O' }} score={5}.

Show a solution

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

export function Player({ player, score }) {
  return (
    <p className="player">
      {player.name} ({player.mark}): {score} points
    </p>
  );
}

export default function Scoreboard() {
  return (
    <section>
      <h2>Scores</h2>
      <Player player={{ name: 'Ada', mark: 'X' }} score={3} />
      <Player player={{ name: 'Grace', mark: 'O' }} score={5} />
    </section>
  );
}
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 function Player({ player, score }) {
  // TODO: show the name, the mark in brackets and the score, e.g. Ada (X): 3 points
  return <p className="player">Player</p>;
}

export default function Scoreboard() {
  // TODO: give the second Player Grace, with the mark O and a score of 5.
  return (
    <section>
      <h2>Scores</h2>
      <Player player={{ name: 'Ada', mark: 'X' }} score={3} />
      <Player />
    </section>
  );
}

main.test.jsx

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

test('The scoreboard shows Ada (X): 3 points', () => {
  render(<Scoreboard />);
  assert.ok(screen.queryByText('Ada (X): 3 points'), 'no element shows the text Ada (X): 3 points');
});

test('The scoreboard shows Grace (O): 5 points', () => {
  render(<Scoreboard />);
  assert.ok(screen.queryByText('Grace (O): 5 points'), 'no element shows the text Grace (O): 5 points');
});

test('Player shows the props it is given', () => {
  render(<Player player={{ name: 'Lin', mark: 'O' }} score={0} />);
  assert.ok(screen.queryByText('Lin (O): 0 points'), 'Player with Lin, O and 0 does not show Lin (O): 0 points');
});

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

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

Forgetting the braces around the prop name

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

export default function Board() {
  return <Square value="1" />;
}

What React prints

Objects are not valid as a React child (found: object with keys {value}).

Why, and the fix

React calls Square with one object holding all props. Without braces, the parameter value is that whole object, { value: "1" }, and {value} tries to show an object, which React refuses: the run stops with Uncaught Error. Destructure the prop: function Square({ value }).

Passing a number without braces

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

export default function Board() {
  return <Square value=5 />;
}

What React prints

error TS1145: '{' or JSX element expected.

Why, and the fix

A JSX attribute takes a string in quotes or a JavaScript value in braces, nothing else, so the JSX transform stops with this error and nothing runs. Write value={5} for the number 5, or value="5" for the string.

Reading a prop under a different name

function Profile({ person }) {
  return <h1>{person.name}</h1>;
}

export default function App() {
  return <Profile user={{ name: 'Ada' }} />;
}

What React prints

TypeError: Cannot read properties of undefined (reading 'name')

Why, and the fix

App passes the prop user, but Profile reads person. A prop that was not passed is undefined, and undefined has no name. Use the same name on both sides: <Profile person={…} /> or function Profile({ user }). This is the V8 wording (Chrome, Node.js); Firefox and Safari word the TypeError differently.

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

Props pass data from parent to child

In B1.1 every Square showed the same thing. Props fix that. The parent writes values on the child's tag, like HTML attributes: <Square value="1" />. Each such attribute is a prop. The child receives them and decides what to do with them, for example show the value in its button. Data flows one way: down, from the component that renders the tag (the parent, Board) to the component it renders (the child, Square). A child cannot reach up and change what its parent passed.

The child reads props by destructuring

React calls your component with one argument: an object that holds every prop. function Square({ value }) takes the value property out of that object, so value is the string "1". You can also write function Square(props) and use props.value; it is the same object. Forgetting the braces, function Square(value), names the whole object value, and showing it with {value} makes React throw an error. A prop the parent did not pass, or spelled differently, is undefined in the child, and {undefined} shows nothing.

Strings in quotes, everything else in braces

value="1" passes the string 1. Any other value goes in curly braces: score={3} passes the number 3, player={player} passes an object, and player={{ name: 'Ada' }} writes the object right there (the outer braces switch to JavaScript, the inner ones are the object). value=5 without braces does not compile. Because each tag gets its own props, one Square can show nine different values. Lesson B2.3 adds children, default values and why a component must not change its props.

Sources

Last reviewed October 5, 2026