Warm-up · Activity 1 of 7
Warm-up from lesson B2.3: children and a default value. What text does App show?
function Label({ color = 'gray', children }) {
return <b>{children} ({color})</b>;
}
function App() {
return <Label>Sale</Label>;
}// B2.4 · ~30 min · Beginner
After this lesson you show different JSX depending on a condition with if, null, the ternary ? : and &&, without a stray 0.
Warm-up · Activity 1 of 7
function Label({ color = 'gray', children }) {
return <b>{children} ({color})</b>;
}
function App() {
return <Label>Sale</Label>;
}Predict · Activity 2 of 7
const messageCount = 0;
function Inbox() {
return <div>{messageCount && <p>New messages</p>}</div>;
}Practice · Activity 3 of 7
<li>{name} {isPacked ____ '✅'}</li>Practice · Activity 4 of 7
Practice · Activity 5 of 7
function Item({ name, isPacked }) {
let content = name;
if (isPacked) {
content = <del>{name}</del>;
}
return <li>{content}</li>;
}Brain teaser · Activity 6 of 7
function Item({ name, isPacked }) {
if (isPacked) {
return <li>{name} ✅</li>;
}
}
function PackingList() {
return (
<ul>
<Item name="Tent" isPacked={true} />
<Item name="Map" isPacked={false} />
</ul>
);
}Apply · Activity 7 of 7
Check your work against this list
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 packing list in the style of react.dev's example, with all four tools. Item strikes out packed items with a ternary. Note returns null when it has no text, so the empty note leaves no trace. Summary starts a let variable with one message and replaces it when something is left. The last line uses urgent > 0 &&, so with urgent = 0 nothing appears, not a 0.
main.jsx
function Item({ name, isPacked }) {
return (
<li className="item">
{isPacked ? <del>{name + ' ✅'}</del> : name}
</li>
);
}
function Note({ text }) {
if (!text) {
return null;
}
return <p className="note">{text}</p>;
}
function Summary({ left }) {
let message = 'Everything is packed.';
if (left > 0) {
message = left + ' left to pack.';
}
return <p>{message}</p>;
}
export default function PackingList() {
const urgent = 0;
return (
<section>
<h1>Alex's Packing List</h1>
<ul>
<Item name="Tent" isPacked={true} />
<Item name="Sleeping bag" isPacked={true} />
<Item name="Map" isPacked={false} />
</ul>
<Summary left={1} />
<Note text="" />
{urgent > 0 && <p>{urgent} urgent items!</p>}
</section>
);
}
Run it with
npm run devOutput
<!-- #root -->
<div id="root"><section><h1>Alex's Packing List</h1><ul><li class="item"><del>Tent ✅</del></li><li class="item"><del>Sleeping bag ✅</del></li><li class="item">Map</li></ul><p>1 left to pack.</p></section></div>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.
Exercise 1 of 2
Every item in Alex's list looks the same. Change Item so that a packed item shows its name and a check mark, struck out with <del>, like <del>Tent ✅</del>, and an item that is not packed shows only its name. Use the ternary ? : inside the <li>. Keep the name Item and its export: the tests render it with their own 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.
Inside the <li>, write {isPacked ? … : …}: the first part for packed items, the second for the others.
The packed part is JSX: <del>{name + ' ✅'}</del>. The other part is just name.
The whole line: {isPacked ? <del>{name + ' ✅'}</del> : name}.
One way to solve it. Yours can look different and still pass the checks.
export function Item({ name, isPacked }) {
return (
<li className="item">
{isPacked ? <del>{name + ' ✅'}</del> : name}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Alex's Packing List</h1>
<ul>
<Item name="Tent" isPacked={true} />
<Item name="Sleeping bag" isPacked={true} />
<Item name="Map" isPacked={false} />
</ul>
</section>
);
}
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
// TODO: a packed item shows its name and a check mark, struck out with
// <del>: <del>Tent ✅</del>. An item that is not packed shows only its name.
export function Item({ name, isPacked }) {
return <li className="item">{name}</li>;
}
export default function PackingList() {
return (
<section>
<h1>Alex's Packing List</h1>
<ul>
<Item name="Tent" isPacked={true} />
<Item name="Sleeping bag" isPacked={true} />
<Item name="Map" isPacked={false} />
</ul>
</section>
);
}
main.test.jsx
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import PackingList, {Item} from './main.jsx';
test('A packed item shows its name with a check mark', () => {
render(<Item name="Tent" isPacked={true} />);
const item = screen.getByRole('listitem').textContent;
assert.equal(item, 'Tent ✅', 'the item reads ' + JSON.stringify(item));
});
test('A packed item is struck out with <del>', () => {
render(<Item name="Tent" isPacked={true} />);
const del = screen.getByRole('listitem').querySelector('del');
assert.ok(del, 'there is no <del> in the packed item');
assert.equal(del.textContent, 'Tent ✅', 'the <del> reads ' + JSON.stringify(del.textContent));
});
test('An item that is not packed shows only its name', () => {
render(<Item name="Map" isPacked={false} />);
const item = screen.getByRole('listitem');
assert.equal(item.textContent, 'Map', 'the item reads ' + JSON.stringify(item.textContent));
assert.equal(item.querySelector('del'), null, 'the item that is not packed is struck out');
});
test('The list strikes out two of its three items', () => {
render(<PackingList />);
const struck = screen.getAllByRole('listitem').filter((li) => li.querySelector('del')).length;
assert.equal(struck, 2, 'struck-out items: ' + struck);
});
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 devThere 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
Inbox shows how many new messages there are. Run it: the empty inbox shows a 0 under its heading, and one message is called "messages". Fix both: with 0 messages, show nothing below the heading; with 1, write "You have 1 new message"; otherwise "You have 3 new messages". Keep the name Inbox and its export.
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.
count && … gives 0 when count is 0, and React shows the number. Put a boolean on the left.
count > 0 && <p>…</p> renders nothing for 0, because count > 0 is false.
For the word, use a ternary inside the paragraph: {count === 1 ? 'message' : 'messages'}.
One way to solve it. Yours can look different and still pass the checks.
export function Inbox({ count }) {
return (
<div>
<h2>Inbox</h2>
{count > 0 && <p>You have {count} new {count === 1 ? 'message' : 'messages'}</p>}
</div>
);
}
export default function App() {
return (
<>
<Inbox count={3} />
<Inbox count={1} />
<Inbox count={0} />
</>
);
}
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
// TODO: with 0 messages, show nothing below the heading (no stray 0).
// With 1 message, say "message"; otherwise "messages".
export function Inbox({ count }) {
return (
<div>
<h2>Inbox</h2>
{count && <p>You have {count} new messages</p>}
</div>
);
}
export default function App() {
return (
<>
<Inbox count={3} />
<Inbox count={1} />
<Inbox count={0} />
</>
);
}
main.test.jsx
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import {Inbox} from './main.jsx';
test('3 messages: You have 3 new messages', () => {
render(<Inbox count={3} />);
const line = screen.getByText(/^You have/).textContent;
assert.equal(line, 'You have 3 new messages', 'the line reads ' + JSON.stringify(line));
});
test('1 message: You have 1 new message', () => {
render(<Inbox count={1} />);
const line = screen.getByText(/^You have/).textContent;
assert.equal(line, 'You have 1 new message', 'the line reads ' + JSON.stringify(line));
});
test('0 messages: only the heading, no stray 0', () => {
const {container} = render(<Inbox count={0} />);
assert.equal(container.textContent, 'Inbox', 'the inbox reads ' + JSON.stringify(container.textContent));
});
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 devThere is no command for the checks on your computer yet. They are in main.test.jsx, written with node:test and React Testing Library.
function Item({ name, isPacked }) {
return (
<li className="item">
{if (isPacked) { name + ' ✅' }}
</li>
);
}
export default function PackingList() {
return (
<ul>
<Item name="Tent" isPacked={true} />
</ul>
);
}
What React prints
error TS1109: Expression expected.Why, and the fix
Curly braces in JSX take an expression, and if is a statement, so the JSX transform stops and nothing runs. The full line starts with the position, main.jsx(4,8), and a second error, TS1381, follows. Use the ternary inside the braces, {isPacked ? name + ' ✅' : name}, or put the if before the return.
function Item({ name, isPacked }) {
return <li className="item">{name} {isPacked ? '✅'}</li>;
}
export default function PackingList() {
return (
<ul>
<Item name="Tent" isPacked={true} />
</ul>
);
}
What React prints
error TS1005: ':' expected.Why, and the fix
The conditional operator always has three parts: condition ? then : otherwise. The full line starts with main.jsx(2,53). Add the otherwise value, {isPacked ? '✅' : ''}, or, when there is nothing to show otherwise, use && instead: {isPacked && '✅'}.
function Item({ name, isPacked }) {
const content = name;
if (isPacked) {
content = name + ' ✅';
}
return <li className="item">{content}</li>;
}
export default function PackingList() {
return (
<ul>
<Item name="Tent" isPacked={true} />
</ul>
);
}
What React prints
TypeError: Assignment to constant variable.Why, and the fix
A const cannot be reassigned, so the if throws while Tent renders, and the run prints Uncaught TypeError (Chrome and Node wording; Firefox and Safari word it differently). Declare the variable with let, as react.dev does: let content = name;.
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
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.