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

JavaScript in general

  • 01 Variables, types and operators0/2
  • 02 Functions and closures0/2
  • 03 Objects0/2
  • 04 Arrays I: map, filter and friends0/2
  • 05 Arrays II: reduce, sorting and grouping0/3
  • 06 Conditions and logical operators0/2
  • 07 Asynchronous JavaScript0/2
  • 08 Modules and built-in tools0/2

JavaScript for React

  • 09 References and immutability0/3
  • 10 JS patterns you meet everywhere in React0/2
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

JavaScript 10 · JavaScript for React

JS patterns you meet everywhere in React

Lists with map, conditions in JSX, functions in onClick, state updates and closures in components.

Loading lesson…

💬 Found a mistake, something unclear, or have an idea? Let me know.
← References and immutabilityWhere the type goes (TypeScript syntax) →

Before we start: React is mostly JavaScript

At first sight the line tasks.map((t) => (t.id === id ? { ...t, done: !t.done } : t)) looks like a React spell. Yet there's no React in it at all: an arrow function, a ternary, an object spread and an array method. Everything from the previous lessons.

This lesson is the bridge between JavaScript and React. We'll go through exactly where the individual pieces of JS show up in components – and in the next lessons you'll recognise them at a glance. You don't need to understand React in depth yet; lessons React 01 and 2 explain useState and JSX in detail. Here it's about the JavaScript around them.

ℹ️ What's in this lesson
  1. A component is a function, props are an object.
  2. JSX: where an expression goes and why if doesn't fit there.
  3. Lists: map, filter and key.
  4. Conditional rendering: ternary, &&, early return, dictionary.
  5. Events: a function as a value in onClick.
  6. State: array destructuring and immutable updates.
  7. Closures in components: every render is a snapshot.
  8. Async in an event handler.

1. A component is a function, props are an object

// A component = an ordinary function (with a capital letter) that returns JSX
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>
}
// Destructuring the parameter (lesson JavaScript 03) + a default value (lesson JavaScript 02)
function Greeting({ name, greeting = 'Hello' }) {
return <h1>{greeting}, {name}!</h1>
}
// Usage: the attributes are collected into ONE props object
<Greeting name="Anna" /> // props = { name: 'Anna' }
<Greeting name="Peter" greeting="Hi" /> // props = { name: 'Peter', greeting: 'Hi' }

Rest and spread with props

// Take out what you need and pass the rest on
function Button({ variant = 'primary', ...rest }) {
return <button className={`btn btn-${variant}`} {...rest} />
}
<Button variant="ghost" onClick={save} disabled>Save</Button>
// rest = { onClick: save, disabled: true, children: 'Save' } → <button> gets all of it

2. JSX: a window into JavaScript

Curly braces in JSX are a window through which you insert an expression – anything that has a value. Statements (if, for, const) don't belong inside; you write them above the return.

function Price({ amount, discount }) {
// ✅ Statements and intermediate calculations above return
const final = amount - discount
const isFree = final <= 0
return (
<p>
${final} {/* a variable */}
${amount * 1.2} with tax {/* a calculation */}
{formatPrice(final)} {/* a function call */}
{isFree ? 'Free!' : `Price: $${final}`} {/* a ternary, a template */}
{/* {if (isFree) …} ❌ if isn't an expression */}
</p>
)
}
// A template string for combined CSS classes
<li className={`task ${done ? 'task-done' : ''} ${urgent ? 'task-urgent' : ''}`}>
// An object with styles – the outer { } is the window into JS, the inner { } is an object
<div style={{ color: 'red', fontSize: 14 }}>

3. Lists: map, filter and key

There's no for in JSX. A list is rendered by turning an array of data into an array of elements with map – React can render an array of elements:

const fruits = ['apple', 'pear', 'plum']
<ul>
{fruits.map((fruit) => <li key={fruit}>{fruit}</li>)}
</ul>
// map returns [<li>apple</li>, <li>pear</li>, <li>plum</li>]
function TodoList({ todos, query }) {
// filter above return – derived data
const visible = todos.filter((t) => t.title.toLowerCase().includes(query.toLowerCase()))
if (visible.length === 0) return <p>Nothing found.</p> // early return for the empty state
return (
<ul>
{visible.map((todo) => (
<li key={todo.id}> {/* key: a stable id from the data */}
{todo.title}
</li>
))}
</ul>
)
}
⚠️ Two mistakes with map in JSX
{todos.map((t) => { <li key={t.id}>{t.title}</li> })} // ❌ { } without return → nothing is rendered
{todos.map((t) => <li key={t.id}>{t.title}</li>)} // ✅
{todos.map((t) => ( // ✅ several lines → parentheses
<li key={t.id}>{t.title}</li>
))}
{todos.forEach((t) => <li>{t.title}</li>)} // ❌ forEach returns undefined
Lesson React 01 explains why key is required and why an index as the key does harm.

4. Conditional rendering

I want…Syntax
two versions{isLoggedIn ? <Profile /> : <Login />}
something, or nothing{hasError && <ErrorBox />}
to render nothing at allif (!user) return null
loading / error / dataan early return for each state
a version based on a value{ICONS[status]}
function UserPanel({ user, isLoading, error }) {
// early return – deal with each state and stop (lesson JavaScript 06)
if (isLoading) return <p>Loading…</p>
if (error) return <p className="error">{error}</p>
if (!user) return null // null = render nothing
return (
<div>
<h2>{user.name}</h2>
{user.isAdmin && <span>Admin</span>} {/* && with a boolean */}
{user.messages.length > 0 && ( {/* ✅ a comparison, not a bare number */}
<span>{user.messages.length} messages</span>
)}
<p>{user.bio ?? 'No description'}</p> {/* ?? for a default text */}
<p>{user.address?.city}</p> {/* ?. for optional details */}
</div>
)
}
⚠️ The zero trap
{count && <Badge />} renders a 0 on the page when count = 0. && returns the left side (lesson JavaScript 06) and React renders numbers. Write {count > 0 && <Badge />}.
Demo

JavaScript inside a component

What you see: A small catalogue. In the code (the Code button) you'll find filter, map with key, a ternary, &&, a template string for a CSS class, a dictionary of icons and reduce for a sum – all commented.

Try it:

  1. Type into the search – the list is filtered and the price total recalculated.
  2. Tick Only in stock: the second filter in the chain.
  3. Type nonsense, say "xyz". The ternary switches to the message "Nothing found".
  4. Open the code and find every pattern from lessons JavaScript 04–06.

The takeaway: A component is largely plain JavaScript: the data is prepared above the return (filter, reduce) and in JSX it's only put together (map, ternary, &&).

Loading the interactive part…

5. Events: a function as a value

You pass onClick a function that React calls on a click (lesson JavaScript 02) – not the result of calling it:

function handleClick() {
alert('Clicked!')
}
<button onClick={handleClick}>OK</button> // ✅ you pass the function
<button onClick={handleClick()}>OK</button> // ❌ you call it RIGHT AWAY while rendering
<button onClick={() => remove(todo.id)}>×</button> // ✅ you need an argument → wrap it in an arrow
<button onClick={remove(todo.id)}>×</button> // ❌ it gets deleted while rendering!

The event object

// React passes the handler an event object
<input onChange={(e) => setName(e.target.value)} /> // the text from the input (always a string!)
<input type="checkbox" onChange={(e) => setOn(e.target.checked)} />
<input type="number" onChange={(e) => setAge(Number(e.target.value))} /> // conversion (lesson JavaScript 01)
function handleSubmit(e) {
e.preventDefault() // otherwise the form would reload the page
// …
}
<form onSubmit={handleSubmit}>

A callback from the parent

// The parent passes a function as a prop (convention: on…), the child calls it (convention: handle…)
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li>
<input type="checkbox" checked={todo.done} onChange={() => onToggle(todo.id)} />
{todo.title}
<button onClick={() => onDelete(todo.id)}>×</button>
</li>
)
}
<TodoItem todo={todo} onToggle={handleToggle} onDelete={handleDelete} />

6. State: destructuring and immutable updates

const [count, setCount] = useState(0)
// useState returns an array [value, function to change it] – array destructuring (lesson JavaScript 03)
// you choose the names; the convention is x / setX

State is never changed directly. You always pass a new value – for objects and arrays a new object or a new array (the whole of lesson JavaScript 09):

setCount(count + 1) // a number
setName(e.target.value) // text
setUser({ ...user, name: 'Eve' }) // an object – spread
setTodos([...todos, newTodo]) // add
setTodos(todos.filter((t) => t.id !== id)) // remove
setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t))) // change one
todos.push(newTodo) // ❌ a mutation – React doesn't notice the change
setTodos(todos) // ❌ still the same array

The updater: a function instead of a value

setCount(count + 1) // a new value calculated from count in THIS render
setCount((c) => c + 1) // a function: "take the current value and add 1"
setTodos((prev) => [...prev, newTodo]) // the same for an array

Why are there two forms? Because of closures – see the next section.

7. Closures in components: every render is a snapshot

A component is a function, and React calls it again on every render. Every call has its own variables – its own count, its own handleClick. Functions created in one render carry the values from that render in their backpack (closure, lesson JavaScript 02). It's like a photo: it captures the state at the moment it was taken.

function Counter() {
const [count, setCount] = useState(0)
function addLater() {
setTimeout(() => {
setCount(count + 1) // count is the value from the render in which you clicked
}, 2000)
}
// Click 3 times quickly: all three timeouts see count = 0 → the result is 1, not 3
function addLaterFixed() {
setTimeout(() => {
setCount((c) => c + 1) // React passes the current value → the result is 3
}, 2000)
}
}
Demo

Snapshot vs. updater

What you see: A counter and two buttons that add one only after 2 seconds – each in a different way.

Try it:

  1. Click ❌ three times quickly and wait. The number goes up only by 1: all three timeouts carry the same snapshot of count.
  2. Reset and click ✅ three times quickly. The number goes up by 3: the updater always gets the current value.

The takeaway: When the new value depends on the previous one and changes can pile up (timers, several changes in a row), use the updater setX((prev) => …).

Loading the interactive part…

❓ Why doesn't count change right after setCount?

count is an ordinary const variable of this function call. setCount doesn't overwrite it – it just tells React "next time, call the component with the new value". You'll see the new count only in the next render. Lesson React 02 goes into it in detail.

8. Async in an event handler

An event handler can be an async function – typically submitting a form (lesson JavaScript 07):

function SignupForm() {
const [isSending, setIsSending] = useState(false)
const [error, setError] = useState(null)
async function handleSubmit(e) {
e.preventDefault()
setIsSending(true)
setError(null)
try {
await registerUser({ name, email }) // wait for the server
alert('Done!')
} catch (err) {
setError(err.message) // show the error
} finally {
setIsSending(false) // loading is over either way
}
}
return (
<form onSubmit={handleSubmit}>
…
<button disabled={isSending}>{isSending ? 'Sending…' : 'Sign up'}</button>
{error && <p className="error">{error}</p>}
</form>
)
}
⚠️ The component itself must not be async
async function Profile() would return a Promise instead of JSX. Loading data when a component appears is done with effects (lesson React 03) or a library (lesson React 13).

Check yourself

Quiz What does <button onClick={deleteAll()}> do?
Quiz Why can't you write {if (x) …} in JSX?
Quiz How do you add an item to the todos state?
Quiz You click a button with setTimeout(() => setCount(count + 1), 1000) 3 times quickly. What will count be (starting at 0)?

Warm-up

Challenge

A list of names

Warm-up

Render an array of names as a list and show how many there are.

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

Challenge

Shopping list

MediumBonus · free after sign-in

A component with ready-made state and looks, in which the JavaScript is missing: filtering, counting, adding, ticking off and deleting an item. Every one of them is a pattern from this part of the course. What exactly the list should do is in the comments in the code.

🔓 Bonus challenge – free after sign-in

Medium and hard challenges unlock once you sign in. Like the whole course, they are free – just an e-mail, no password and no payment.

How we handle your data

Summary

  • A component is a function, props are an object – destructuring, default values and rest/spread work as with any function.
  • Expressions go into JSX. Statements and calculations above the return; in JSX a ternary, &&, map and template strings.
  • A list = map to an array of elements with a key. Filter above the return, handle the empty state with a ternary or an early return.
  • onClick={fn} passes the function, onClick={fn()} calls it right away. With an argument () => fn(x).
  • State changes only through the setter and with a new value – spread, filter, map. Never push or assignment.
  • Every render is a snapshot. Functions carry the values from their render; when changes pile up, use the updater setX((prev) => …).