Skip to content
React Course1.0.0 beta
CSEN
React Course1.0.0 beta
CSEN
🏠 Home🧭 Where to start?🔁 Review📄 Cheat sheets📰 What's newℹ️ About the course
JavaScript0/22
TypeScript0/6

Basics

  • 01 A quick review of the basics0/3
  • 02 State and events (useState)0/3

Hooks in depth

  • 03 Effects (useEffect)0/3
  • 04 Refs (useRef)0/3
  • 05 More complex state (useReducer)0/3
  • 06 Context0/3
  • 07 Custom hooks0/3

Forms

  • 08 Forms and React 19 Actions0/3

Performance

  • 09 Performance and rendering0/3

Patterns

  • 10 Component design patterns0/3

Ecosystem

  • 11 TypeScript with React0/3
  • 12 Routing (React Router)0/3
  • 13 Data fetching0/3
  • 14 Application state management0/3

Project

  • 15 Final project: Kanban0/2

Summary

  • 16 Summary: the principles of React

Your account

Privacy

React 01 · Basics

A quick review of the basics

JSX, components, props, children, conditions, lists and why key matters.

Loading lesson…

💬 Found a mistake, something unclear, or have an idea? Let me know.
← Types that change (unions and narrowing)State and events (useState) →

Before we start: React is a kitchen with recipes

This lesson is a quick review of the basics. Even if you know them a little, go through it – it introduces ideas the whole course builds on. And the key demo at the end catches out experienced developers too.

A recipe instead of instructions

Picture a restaurant. A guest doesn't order with instructions ("heat the pan, add butter, crack two eggs…"). They say what they want: "a ham omelette". How it's made is the kitchen's business.

That's exactly how React works. You don't tell it how to change the page ("find this paragraph, rewrite its text, add a class"). You describe what the page should look like, and React makes sure it looks like that. This approach is called declarative: you describe the result, not the steps.

A component is a recipe, props are the order

There's one omelette recipe, but hundreds of omelettes get made – each a little different: with cheese, without onion, a big portion. So the recipe expects the guest's wishes.

  • A component is the recipe: a function that describes how a piece of the screen should be "cooked".
  • Props are the order: details the recipe gets from outside ("name: Anna, role: Frontend").
  • JSX is the finished dish: a description of what should be displayed.

The same order should always produce the same dish. And the cook doesn't rewrite the order – they can't change what the guest asked for. These are the two most important rules of components, and you'll see them in code in a moment.

The whole app is a construction kit

A big menu is made of smaller recipes: "the lunch menu" = soup + main course + dessert. An app is made of components in the same way: a page contains a header, a list and a footer, the list contains items, an item contains a button. Each component handles only its own piece, and they fit into each other like building blocks.

ℹ️ What's in this lesson
  1. From the kitchen to code: the first component step by step.
  2. JSX and what it turns into.
  3. Props: how to pass data to a component.
  4. Children: how to pass a whole piece of content to a component.
  5. Conditions: how to show something only sometimes.
  6. Lists and key: how to render an array of data and why the key matters so much.
Each section starts with a short explanation, then comes a typical example broken down step by step, and finally a live demo.

From the kitchen to code

The analogy is nice, but what does it look like in code? We'll go through it in small steps – from plain HTML to the first component. We won't skip a single step.

Step 1: the finished dish (plain HTML)

This is what a greeting on a page looks like in pure HTML:

<section class="welcome">
<h1>Hello, Anna!</h1>
<p>Good morning</p>
</section>

It's a hard-coded finished dish. Always the same – for Anna, in the morning. If we wanted to greet Peter in the afternoon, we'd have to write the whole HTML again.

Step 2: instructions (the way we don't want to do it)

Without React you'd change the page with JavaScript step by step – exactly like a guest dictating instructions to the cook:

const section = document.createElement('section') // take a pan
const h1 = document.createElement('h1') // crack an egg
h1.textContent = 'Hello, Anna!'
section.append(h1)
document.body.append(section)
// …and when the name changes, you have to remember where h1 is and rewrite it yourself:
h1.textContent = 'Hello, Peter!'

For one heading it's fine. For a whole app you quickly get lost: what all has to be touched when something changes?

Step 3: a recipe (the first component)

React turns this around. Instead of instructions you write a function that returns what a piece of the page should look like. The HTML moves inside JavaScript – this notation is called JSX:

function Welcome() {
return (
<section className="welcome">
<h1>Hello, Anna!</h1>
<p>Good morning</p>
</section>
)
}
  • function Welcome() is the recipe – an ordinary function. Its name starts with a capital letter.
  • return ( … ) returns a description of the dish: what the result should look like. Nothing is rendered here yet.
  • Inside is almost the same HTML as in step 1. The only difference at first glance: className instead of class (JavaScript has the word class taken).

Step 4: a recipe with an order

So far the recipe only knows Anna. Let's give it an order – a parameter with the name. You know the type after the colon ({ name: string }) from lesson TypeScript 01: the order must contain the name as text. Inside curly braces { } you can then put a value from JavaScript into JSX:

function Welcome({ name }: { name: string }) {
return (
<section className="welcome">
<h1>Hello, {name}!</h1>
</section>
)
}

The recipe is ready. The order goes somewhere else – into the JSX of another component that wants to show the greeting. Here it's the main component App, which uses the recipe twice:

function App() {
return (
<div>
{/* One recipe, two orders → two dishes */}
<Welcome name="Anna" />
<Welcome name="Peter" />
</div>
)
}

<Welcome name="Anna" /> looks like an HTML tag, and that's how the component is used. The name attribute is passed to it as the order – in React this is called props (in detail in section 2).

Step 5: who does the cooking

A recipe cooks nothing by itself. Somewhere at the start of the app (in the main.tsx file) there's a single line that tells React: "here's the main recipe, cook it into this spot on the page":

createRoot(document.getElementById('root')).render(<App />)

From then on React does the cooking. It calls App, which contains other components (say Welcome), React calls those too – and builds the page from their descriptions. When something changes later, it calls the recipes again and updates only what really differs on the page. It writes the instructions from step 2 for you.

A translation table

In the kitchenIn ReactIn code
a recipea componentfunction Welcome() { … }
an orderprops<Welcome name="Anna" />
the finished dishJSX – a description of the resultreturn <h1>Hello, {name}!</h1>
the cookReactrender(<App />)
a menu made of dishesa component tree<App> → <Welcome> → …
Demo

One recipe, different orders

What you see: Two cards rendered by the same Welcome component. The left one gets its order from the fields, the right one has a fixed order (Peter, afternoon).

Try it:

  1. Change the name. The left card changes as you type – the recipe is "cooked" again with the new order.
  2. Untick "morning". Only the greeting text changes, the rest of the card stays.
  3. Look at the right card: the same recipe, a different order, a different dish.
  4. Open the code with the Code button. Welcome is an ordinary function that returns JSX.

The takeaway: A component is a recipe: it describes what the result should look like for a given order. The cooking – and the re-cooking after every change – is React's job.

Loading the interactive part…

Now you know what a component is and what it's for. In section 1 we'll look at what JSX actually is and what rules it has.

1. JSX and the first component

The JSX from step 3 looks like HTML written in the middle of JavaScript. In reality it's just a more convenient way of writing a function call. Before it runs, it's compiled into ordinary JavaScript that creates an object describing the element:

// You write this:
<Button color="blue">Save</Button>
// And this is what comes out of it:
jsx(Button, { color: 'blue', children: 'Save' })
// → { type: Button, props: { color: 'blue', children: 'Save' } }

A component returns exactly such a description. It doesn't draw anything itself – it returns an "order for the screen" and React does the rendering. A typical first component – the definition, which you write once (say in the file Welcome.tsx):

// ① THE DEFINITION of the component (the recipe)
function Welcome() {
const name = 'Anna'
const hour = new Date().getHours()
return (
<section className="welcome">
<h1>Hello, {name}!</h1>
<p>{hour < 12 ? 'Good morning' : 'Good afternoon'}</p>
</section>
)
}

The definition on its own shows nothing. The component has to be used – in another component's JSX it's written as a tag. Here the main component App uses it:

// ② USING the component – in another component's JSX it's written as a tag
function App() {
return (
<main>
<Welcome />
</main>
)
}
❓ A component is a function – so why don't I call it as Welcome()?

Because you don't call it – React does. Writing <Welcome /> doesn't run the function. It just creates a description "Welcome goes here" (the { type: Welcome, props: {} } object from the code above). React then decides itself when and how many times to call the function.

That matters a lot: only a component called by React can have its own state and hooks (next lesson). If you wrote {Welcome()}, it would look the same, but React wouldn't know about any component. The state inside would break and keys wouldn't work properly either.

Let's go through what's important about it:

  • The name starts with a capital letter. That's how React tells a component from an HTML tag. <welcome /> would be treated as an unknown HTML element.
  • Curly braces are a window into JavaScript. Inside {…} there can be any expression – a variable, a calculation, a function call, a ternary. Statements like if or for don't fit there, though; they belong above the return.
  • It returns one root element. When you need to return two siblings without a wrapper, use a fragment <>…</>.
  • The attributes are JavaScript ones: className instead of class, htmlFor instead of for, events in camelCase (onClick) and styles as an object (style={{ color: 'red' }}).
💡 Why the declarative approach is better
It removes a whole class of bugs of the "I forgot to update this piece of the page" kind. You only need to describe the result from the data correctly – React works out how to update the page.

2. Props: the order for a component

The Welcome component above can only greet Anna. That's like a recipe for "an omelette for Anna". We want a recipe that greets anyone – the name has to come from outside. That's what props are for.

Props are simply the function's arguments. React collects all the attributes you write on the component's tag into one object and passes it as the first parameter.

Code with props always has two parts in two different places: ① the recipe – the component's definition, which you write once (usually in its own file), and ② the order – the place where you use the component in another component's JSX. The recipe first:

// ① THE RECIPE – the component's definition, say in the file ProfileCard.tsx
// Which props the component accepts (the shape of the order):
interface ProfileCardProps {
name: string
role?: string // question mark = optional
avatar?: string
}
// ↓ React puts here the attributes you write on the tag in ②
function ProfileCard({ name, role = 'Developer', avatar = '🙂' }: ProfileCardProps) {
return (
<div className="card">
<span>{avatar}</span>
<strong>{name}</strong>
<div>{role}</div>
</div>
)
}

And here's the order. The App component wants to show two cards, so it writes the <ProfileCard … /> tag into its JSX twice – each time with different attributes:

// ② THE ORDER – using the component in another component's JSX
function App() {
return (
<div className="row">
{/* Two different orders, one recipe: */}
<ProfileCard name="Anna Smith" role="Frontend" avatar="👩‍💻" />
<ProfileCard name="Peter Brown" />
</div>
)
}

What travels from the order ② into the recipe ①:

Attribute on the tag ②Variable in ProfileCard ①For Peter (no role or avatar)
name="Anna Smith"name is 'Anna Smith'name is 'Peter Brown'
role="Frontend"role is 'Frontend'role is 'Developer' (the default value)
avatar="👩‍💻"avatar is '👩‍💻'avatar is '🙂' (the default value)

What's going on here:

  1. For the first tag in App React collects the attributes into the object { name: 'Anna Smith', role: 'Frontend', avatar: '👩‍💻' } and calls the ProfileCard function with it.
  2. In the function's parameter we unpack (destructure) the object right away into the variables name, role and avatar.
  3. The second tag has neither role nor avatar. The default values written after the equals sign are used – just like in an ordinary JS function.
  4. The result is two different cards from the same code.
❓ What do those curly braces in the function's parameter mean?

That's destructuring, ordinary JavaScript, nothing from React (more in lesson JavaScript 03). The function gets one object (props) and the braces pull the individual properties out into variables right away. These two versions do the same thing:

// with destructuring
function ProfileCard({ name, role = 'Developer' }: ProfileCardProps) {
return <strong>{name} – {role}</strong>
}
// without it
function ProfileCard(props: ProfileCardProps) {
const name = props.name
const role = props.role ?? 'Developer'
return <strong>{name} – {role}</strong>
}

The type : ProfileCardProps after the braces describes the whole props object, not the individual variables. It's exactly the rule from lesson TypeScript 01 (section "2. Where the colon goes and where it doesn't"): the type goes after the closing }, not inside.

⚠️ Props are read-only
The cook doesn't rewrite the guest's order. A component never changes its props:
function ProfileCard(props: ProfileCardProps) {
props.name = props.name.toUpperCase() // ❌ changes data that isn't its own
const shownName = props.name.toUpperCase() // ✅ calculate a new value
}
When something on the screen needs to change, you need state – the topic of the next lesson.

3. Children: content supplied by someone else

Some recipes make only the wrapper and leave the filling to the guest. The baker bakes a tortilla and you say what goes in it. The baker doesn't need to know every possible filling – only how to wrap a tortilla.

In React, that filling is the special prop children. It contains everything you write between the component's opening and closing tag. The component then just decides where to put that content.

// ① THE RECIPE – a card with a title; it gets its filling in children
function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
)
}

And using it in another component. Whatever is between <Card …> and </Card>, Card gets as children:

// ② THE ORDERS – the same card, a different filling each time
function App() {
return (
<div className="row">
<Card title="News">
<p>React 19 is out!</p> {/* ← the first card's children */}
</Card>
<Card title="Team">
<ProfileCard name="Anna" /> {/* ← the second card's children: */}
<ProfileCard name="Peter" /> {/* both cards from section 2 */}
</Card>
</div>
)
}

Card knows nothing about news or the team. It only handles the frame and the heading. Thanks to that you can use it anywhere – this kind of putting together is called composition. The type React.ReactNode means "anything that can be rendered": text, a number, an element, an array of elements, or nothing.

When a component needs several places for content (a header, a body, a footer), it gets more props of type ReactNode. Such places are called slots. Take a panel with buttons in the header and a footer – its recipe first:

// ① THE RECIPE – a panel with three slots: actions, footer and children
interface PanelProps {
title: string
actions?: React.ReactNode // a slot in the header (optional)
footer?: React.ReactNode // a slot in the footer (optional)
children: React.ReactNode // the main content
}
function Panel({ title, actions, footer, children }: PanelProps) {
return (
<section className="card">
<header>
<strong>{title}</strong>
{actions}
</header>
<div>{children}</div>
{footer !== undefined && <footer>{footer}</footer>}
</section>
)
}

And the order – the arrows show what goes where:

// ② THE ORDER
function App() {
return (
<Panel
title="Tasks"
actions={<button>+ Add</button>} // → {actions} in the header
footer={<small>3 tasks</small>} // → {footer} in the footer
>
<ul> {/* → {children} in the middle */}
<li>Go shopping</li>
<li>Clean up</li>
<li>Pay the rent</li>
</ul>
</Panel>
)
}
❓ Do I understand correctly that a slot is an ordinary prop I put something renderable into?

Yes. A "slot" isn't anything special in React, it's just a name for a pattern: an ordinary prop of type ReactNode that the component puts in a certain place in its JSX, just like {title}. ReactNode takes JSX, text, a number, an array of these, and also null, undefined and false, which render nothing. That's why a slot can be optional.

  • children is a slot too. It's just written differently: you put the content between the tags instead of children={…}.
  • A slot vs. a data prop: with title: string, Panel decides how it looks (<strong>{title}</strong>). Into a slot it gets ready-made UI and just places it. Use a slot when whoever uses the component should decide how it looks.
  • An empty slot with a wrapper: {footer} renders nothing when the footer is missing. But if you draw a frame or a line around the slot, you have to write the condition yourself: {footer !== undefined && <footer>{footer}</footer>}. The condition is a real boolean ("a footer was passed") – a bare {footer && …} would fall into the zero trap from section 4 if someone passed the number 0 as the footer.
Demo

Props, default values and children

What you see: Two cards rendered by the same ProfileCard component. The first got all the props plus content between the tags (children). The second got only name.

Try it:

  1. Compare the cards. The second one got the default role "Developer" and the 🙂 avatar, because its order didn't mention them.
  2. Notice the text "Has been writing React since 2018" on the first card. That's children – the component just put it in the prepared spot.
  3. Click </> Code and find the line {children !== undefined && …}. Thanks to it, the second card, which has no filling, doesn't even render an empty wrapper.

The takeaway: One recipe, different orders. Props change the details, children supplies a whole piece of content, and the component doesn't need to know what's inside.

Loading the interactive part…

4. Conditions: showing something only sometimes

A recipe often contains conditions: "if the guest is vegetarian, leave out the ham", "if we're out of eggs, offer pancakes". In JSX conditions are written in ordinary JavaScript – React has no special syntax. You just have to pick the right form for the situation.

A typical example: an inbox icon with the number of new messages.

function Inbox({ user, count }: { user: User | null; count: number }) {
// 1) Early return: when they're not signed in, the rest of the recipe doesn't concern us
if (!user) return <p>Please sign in.</p>
return (
<div>
{/* 2) && – either something, or nothing */}
{count > 0 && <span className="badge">{count}</span>}
{/* 3) a ternary – one of two versions */}
<p>{count > 0 ? 'You have new messages' : 'Nothing new'}</p>
</div>
)
}
SyntaxWhen to use it
if (!data) return …Loading, errors, empty states – the component ends early. The most readable.
cond && <X/>Either something, or nothing. The condition must be a boolean!
cond ? <A/> : <B/>Two versions of the UI.
const map = { a: <A/>, b: <B/> }Several versions by a value (instead of a chain of ternaries).

The zero trap

Why the note "must be a boolean"? Let's try writing the shorter count && … instead of count > 0 && …:

{count && <span className="badge">{count}</span>}
  1. In JavaScript the && operator doesn't return true/false. It returns the left side if it's "falsy", otherwise the right side.
  2. When count is 3, the left side is truthy → the result is the <span>. Fine.
  3. When count is 0, the left side is falsy → the result is the number 0.
  4. React doesn't render false, null or undefined. But it does render numbers – so a lonely "0" appears on the page.

The fix: always put a real boolean into the condition – count > 0, items.length > 0, !!value.

Demo

The zero trap and early return

What you see: At the top, three rows that show the number of messages in three ways: wrong (count &&), right (count > 0 &&) and with a ternary. At the bottom, a user badge written with an early return.

Try it:

  1. At the very start count is 0. In the "Wrong" row you'll see a lonely 0, the "Right" row is empty.
  2. Click Add a message. Now both rows show the same text – the bug is only visible at zero, which is why it's easy to miss.
  3. Click Reset. The zero is back. Meanwhile the ternary switches between 📬 and 📭.
  4. At the bottom, click Sign in and Sign out. When the user is missing, the UserBadge component stops right on its first line and doesn't deal with the rest at all.

The takeaway: Only a boolean belongs in &&. For two versions use a ternary; for "draw nothing / loading / error" an early return.

Loading the interactive part…

5. Lists and why the key matters

Picture the cloakroom in a theatre. You hand in your coat and get a ticket with a number. After the show you get your coat back by the number – even if the attendant has rearranged the hangers in the meantime.

What if the cloakroom worked by position instead of numbers: "your coat is the third from the left"? As long as nothing moves, it works. But as soon as someone hangs a coat at the start of the row, everything shifts and you leave in someone else's coat.

That's exactly what key solves in React. When you render a list, React has to remember between renders which element is which – mainly because elements can have their own state (the text in an input, an expanded detail). key is that numbered ticket.

Example: rendering a list

// The data – an array of tasks
const tasks = [
{ id: 17, label: 'Go shopping' },
{ id: 42, label: 'Tidy up' },
]
// ① THE RECIPE – one row of the list
function TaskRow({ task }: { task: { id: number; label: string } }) {
return <li>{task.label}</li>
}
// ② USING IT – the list: map turns every task into one <TaskRow>
function TaskList() {
return (
<ul>
{tasks.map((task) => (
<TaskRow key={task.id} task={task} />
))}
</ul>
)
}

What in the example is fixed and what did you name yourself? Only two words are fixed – key and map. You can name everything else your own way:

WordWho decides itWhat it means
keyReact – it must be called exactly thisthe cloakroom ticket; React takes it for itself and doesn't pass it to the component
mapJavaScript – an array methodturns every item of the array into one element
tasksyouthe array with the data – todos or items would do just as well
taskyouthe one item map is on right now (the callback parameter)
id, labelyou (or the database)the item's properties; id makes a good key, because it's different for every item
TaskRowyouthe component for one row – it just has to start with a capital letter
task={task}youan ordinary prop: its name on the left, the variable you send into it on the right
  • A list is rendered in JSX with an ordinary map: you turn an array of data into an array of elements. (How map, filter and the other array methods work is explained in lesson JavaScript 04.)
  • key goes on the element that map returns. It must be unique among siblings and stable – the same item must have the same key on every render.
  • The best key is an ID from the data (from the database, or created at the moment the item came into being).
❓ Is key an ordinary prop? Can I read it in the component?

You can't. key (like ref in older versions) is taken by React for itself and isn't passed to the component. TaskRow above finds only task in its props. When the component needs the ID, pass it separately: <TaskRow key={task.id} id={task.id} … />, or take it from the data (task.id).

And one more thing: key belongs on the element map returns, not inside the component. Writing it on the <li> inside TaskRow makes no sense – React looks for it in the array created by map.

What happens with a position-based key

Let's say every row has its own input for a note. In the "Go shopping" row you type "milk". Then you add a new task "Walk the dog" to the start. React compares the old and new rows by key:

KeyBefore addingAfter addingWhere the note "milk" is
key={index}0 = Go shopping, 1 = Tidy up0 = Walk the dog, 1 = Go shopping, 2 = Tidy upat "Walk the dog" – row 0 kept its state
key={task.id}17 = Go shopping, 42 = Tidy up99 = Walk the dog, 17 = Go shopping, 42 = Tidy upat "Go shopping" – row 17 just moved

With the index, React thinks row 0 is still the same row, it just got different text. So it keeps its state – the note "milk". With the ID it knows row 99 is new and just moves rows 17 and 42 along with their state.

Demo

The key={index} bug

What you see: The same task list rendered twice. On the left with key={index}, on the right with key={task.id}. Every row has its own input – its text is the row's state. The button adds a new task to the start of both lists.

Try it:

  1. In both lists, type the word "one" into the input at "Task 1" and "two" at "Task 2".
  2. Click Add to the top. On the left the notes "shift" to the wrong tasks: "one" is now at task 3. On the right they stay with their tasks and the new task has an empty input.
  3. Add one more task. On the left the bug gets worse, on the right everything is fine.

The takeaway: State belongs to the element with the given key. A position-based key gives the state to the wrong element as soon as the order changes.

Loading the interactive part…

📍 When the index may be the key
Only for a list that never gets reordered or filtered, never has items added in the middle or at the start, and whose items have no state of their own – say a static list of links in the footer.
⚠️ Never generate the key during render
key={Math.random()} or key={crypto.randomUUID()} is like giving the attendant a new ticket every time they look at the hanger. Every render would create completely new elements and throw away all their state. Create the ID at the moment the item comes into being (when it's added) and store it with the data.
✨ A trick: key as a reset

key doesn't only work in lists. You can put it on any element, and React decides by it the same way: the same key = the same element (it keeps its state), a different key = a different element (it throws the old one away along with its state and creates a new one). You can use that when you want to "reset" a component.

An example: a page shows a form for writing a message to the selected user. You pick Anna, type "Hi Anna" into the form and then switch to Peter.

// ❌ Without a key: at this place there's still "the same" MessageForm,
// it just got different props – the half-written "Hi Anna" stays in it for Peter too
<MessageForm userId={userId} />
// ✅ With a key: it's a different form for every user
<MessageForm key={userId} userId={userId} />

What React does with the key after switching from Anna to Peter:

  1. Before, there was a MessageForm with the key 'anna' at this place; now it has the key 'peter'. A different key → to React it's not the same element.
  2. It throws away the form with the key 'anna' – half-written text and all.
  3. It creates a new form with the key 'peter', empty, as if the page had just loaded.

In cloakroom terms: a new ticket = a new coat. Peter doesn't get Anna's coat just because it hangs on the same peg. It's handy wherever a component should start from scratch when "who or what it's about" changes – a form, a product detail, a player. The "↺ Reset" button on the course's demos works exactly like this: it changes the demo's key.

Check yourself

Quiz What does {items.length && <List items={items} />} render when the array is empty?
Quiz A component gets the prop title. It needs to show it in capitals. What's right?
Quiz When is it fine to use the index as the key?

Warm-up

Challenge

Name badge

Warm-up

The component already gets its props – it just doesn't print them yet.

Task is in the comments in the code below – at the top of the file and at the places marked TODO.

🧩 Where do the things not defined here come from
CSS classes like row, stack, card, list, btn, input or muted are the course's ready-made styles in src/styles.css (row = side by side, stack = stacked, card = bordered box, muted = grey text).

Loading the interactive part…

Challenges

The challenges follow the order of the sections. The first practises section 3 – you'll write a "wrapper" that knows nothing about its content. The second practises sections 4 and 5 (conditions, lists and keys).

Challenge

A panel with slots

Easy

Write a "tortilla": a panel with a heading that accepts any filling through children, plus optional slots for buttons and a footer. You'll see this pattern in every UI library. What should show at the end is in the comments in the code.

Task is in the comments in the code below – at the top of the file and at the places marked TODO.

🧩 Where do the things not defined here come from
CSS classes like row, stack, card, list, btn, input or muted are the course's ready-made styles in src/styles.css (row = side by side, stack = stacked, card = bordered box, muted = grey text).

Loading the interactive part…

Challenge

A product list

Easy

Render a list of products with prices, highlight the sold-out ones and add an "Only in stock" filter. The checkbox is already prepared (it uses state, which we'll cover in detail in the next lesson). What should show is in the comments in the code.

Task is in the comments in the code below – at the top of the file and at the places marked TODO.

Loading the interactive part…

Summary

  • React is declarative: you describe what the page should look like, not how to change it.
  • A component is a recipe – a function that returns JSX from props. A name with a capital letter, one root element, expressions in {…}.
  • Props are the order: they come from outside and the component only reads them.
  • Children and slots (props of type ReactNode) let a component wrap content it knows nothing about.
  • Conditions: early return, a ternary, or && – but only a boolean into &&, otherwise a 0 gets rendered.
  • The key is the cloakroom ticket: it decides who the state belongs to. A stable ID from the data, the index only for static lists, never a random key.
  • Changing the key resets the component – a useful trick.
🧩 Where do the things not defined here come from (products, Product)
products, Product – from the file src/course/fakeApi.en.ts. The course fake API: data and functions pretending to be a server (with a delay, sometimes even with an error). In a real app you would call fetch() here.
export interface Product {
id: number
name: string
category: 'fruit' | 'vegetables' | 'bakery' | 'dairy'
price: number
inStock: boolean
}
export const products: Product[] = [
{ id: 1, name: 'Apple', category: 'fruit', price: 12, inStock: true },
{ id: 2, name: 'Banana', category: 'fruit', price: 8, inStock: true },
{ id: 3, name: 'Pear', category: 'fruit', price: 15, inStock: false },
{ id: 4, name: 'Carrot', category: 'vegetables', price: 6, inStock: true },
{ id: 5, name: 'Tomato', category: 'vegetables', price: 9, inStock: true },
{ id: 6, name: 'Cucumber', category: 'vegetables', price: 19, inStock: false },
{ id: 7, name: 'Bread roll', category: 'bakery', price: 3, inStock: true },
{ id: 8, name: 'Bread', category: 'bakery', price: 45, inStock: true },
{ id: 9, name: 'Milk', category: 'dairy', price: 22, inStock: true },
{ id: 10, name: 'Cheddar', category: 'dairy', price: 39, inStock: true },
{ id: 11, name: 'Yogurt', category: 'dairy', price: 14, inStock: false },
{ id: 12, name: 'Orange', category: 'fruit', price: 11, inStock: true },
]
CSS classes like row, stack, card, list, btn, input or muted are the course's ready-made styles in src/styles.css (row = side by side, stack = stacked, card = bordered box, muted = grey text).