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
0. JavaScript0/22

Basics

  • 1. A quick review of the basics0/3
  • 2. State and events (useState)0/3

Hooks in depth

  • 3. Effects (useEffect)0/3
  • 4. Refs (useRef)0/3
  • 5. More complex state (useReducer)0/3
  • 6. Context0/3
  • 7. Custom hooks0/3

Forms

  • 8. Forms and React 19 Actions0/3

Performance

  • 9. Performance and rendering0/3

Patterns

  • 10. Component design patterns0/3

Ecosystem

  • 11. Where the type goes (TypeScript syntax)0/3
  • 12. TypeScript with React0/3
  • 13. Routing (React Router)0/3
  • 14. Data fetching0/3
  • 15. Application state management0/3

Project

  • 16. Final project: Kanban0/2

Summary

  • 17. Summary: the principles of React

Your account

Privacy

Lesson 1 · 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.
← JS patterns you meet everywhere in ReactState 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. Inside curly braces { } you can then put a value from JavaScript into JSX:

function Welcome({ name }) {
return (
<section className="welcome">
<h1>Hello, {name}!</h1>
</section>
)
}
// One recipe, two orders → two dishes
<Welcome name="Anna" />
<Welcome name="Peter" />

<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:

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>
)
}
// Usage – a component is written as a tag:
<Welcome />
❓ 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.

interface ProfileCardProps {
name: string
role?: string // question mark = optional
avatar?: string
}
function ProfileCard({ name, role = 'Developer', avatar = '🙂' }: ProfileCardProps) {
return (
<div className="card">
<span>{avatar}</span>
<strong>{name}</strong>
<div>{role}</div>
</div>
)
}
// Two different orders, one recipe:
<ProfileCard name="Anna Smith" role="Frontend" avatar="👩‍💻" />
<ProfileCard name="Peter Brown" />

What's going on here:

  1. For the first tag React collects the attributes into the object { name: 'Anna Smith', role: 'Frontend', avatar: '👩‍💻' } and calls ProfileCard 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 0.3). 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. Exactly where types go in TypeScript is covered in detail in the lesson "Where the type goes".

⚠️ 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.

function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
)
}
// The same card, a different filling each time:
<Card title="News">
<p>React 19 is out!</p>
</Card>
<Card title="Team">
<ProfileCard name="Anna" />
<ProfileCard name="Peter" />
</Card>

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), you pass more props of type ReactNode. Such places are called slots:

<Panel
title="Tasks"
actions={<button>+ Add</button>} // a slot in the header
footer={<small>3 tasks</small>} // a slot in the footer
>
<TaskList /> // children = the main content
</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 && <footer>{footer}</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 && …}. 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

const tasks = [
{ id: 17, label: 'Go shopping' },
{ id: 42, label: 'Tidy up' },
]
<ul>
{tasks.map((task) => (
<TaskRow key={task.id} task={task} />
))}
</ul>
  • 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 0.4.)
  • 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
A changed key means "this is a different element" to React – it throws away the old one along with its state and creates a new one. <ProfileForm key={userId} /> thus gives you a clean form when switching users, without a single extra line. The "↺ Reset" button on the course's demos works exactly like this.

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 (the same text is in the comment at the top of the file)
CHALLENGE: Name badge

The Badge component gets the props `name` and `role`, but it shows question marks for now.

1. Instead of the "?" in <strong>, render the name: {name}
2. Instead of the "?" below it, render the role: {role}
🧩 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.

Task (the same text is in the comment at the top of the file)
CHALLENGE: A reusable Panel (composition with children and "slots")

The <Panel> component has its look and prop types ready. It just doesn't use the props yet.

1. In Panel, render `title` into the <strong> in the header.
2. On the right of the header, render the `actions` slot (it can be missing – then nothing renders).
3. Render `children` into the body of the panel.
4. Render the <footer> ONLY when `footer` exists.
5. In Dashboard, replace the three <div>s with the <Panel> component:
     a) "Revenue" – content `revenue`, footer "Updated today"
     b) "Tasks" – content `taskList`, actions `addButton`
     c) "Note" – just text, no actions and no footer
   The content (revenue, taskList, addButton) is already prepared in variables.
🧩 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). The detailed brief is right below this text.

Task (the same text is in the comment at the top of the file)
CHALLENGE: A product list

Data: the `products` array is imported at the top from src/course/fakeApi.en.ts.
Every product looks like this: { id: 1, name: 'Apple', category: 'fruit', price: 12, inStock: true }

The ProductItem component (one row of the list) and the checkbox are ready.

1. Calculate `visible`: when "Only in stock" is ticked, only products with inStock === true, otherwise all of them.
2. Render `visible` with .map() as <ProductItem> (don't forget the right `key`).
3. In ProductItem: a sold-out product gets the class "muted" and the text "(sold out)" after its name.
4. Below the list, fill in the numbers in "Showing X of Y products".
5. Bonus: group the products by category (an <h4> heading + a list for each).

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).