Lesson 0.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…
Lesson 0.10 · JavaScript for React
Lists with map, conditions in JSX, functions in onClick, state updates and closures in components.
Loading lesson…
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 1 and 2 explain useState and JSX in detail. Here it's about the JavaScript around them.
// A component = an ordinary function (with a capital letter) that returns JSXfunction Greeting(props) {return <h1>Hello, {props.name}!</h1>}// Destructuring the parameter (lesson 0.3) + a default value (lesson 0.2)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' }
// Take out what you need and pass the rest onfunction 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
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 returnconst final = amount - discountconst isFree = final <= 0return (<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 }}>
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 dataconst visible = todos.filter((t) => t.title.toLowerCase().includes(query.toLowerCase()))if (visible.length === 0) return <p>Nothing found.</p> // early return for the empty statereturn (<ul>{visible.map((todo) => (<li key={todo.id}> {/* key: a stable id from the data */}{todo.title}</li>))}</ul>)}
| I want… | Syntax |
|---|---|
| two versions | {isLoggedIn ? <Profile /> : <Login />} |
| something, or nothing | {hasError && <ErrorBox />} |
| to render nothing at all | if (!user) return null |
| loading / error / data | an 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 0.6)if (isLoading) return <p>Loading…</p>if (error) return <p className="error">{error}</p>if (!user) return null // null = render nothingreturn (<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>)}
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:
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…
You pass onClick a function that React calls on a click (lesson 0.2) – 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!
// 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 0.1)function handleSubmit(e) {e.preventDefault() // otherwise the form would reload the page// …}<form onSubmit={handleSubmit}>
// 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} />
const [count, setCount] = useState(0)// useState returns an array [value, function to change it] – array destructuring (lesson 0.3)// 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 0.9):
setCount(count + 1) // a numbersetName(e.target.value) // textsetUser({ ...user, name: 'Eve' }) // an object – spreadsetTodos([...todos, newTodo]) // addsetTodos(todos.filter((t) => t.id !== id)) // removesetTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t))) // change onetodos.push(newTodo) // ❌ a mutation – React doesn't notice the changesetTodos(todos) // ❌ still the same array
setCount(count + 1) // a new value calculated from count in THIS rendersetCount((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.
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 0.2). 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 3function addLaterFixed() {setTimeout(() => {setCount((c) => c + 1) // React passes the current value → the result is 3}, 2000)}}
What you see: A counter and two buttons that add one only after 2 seconds – each in a different way.
Try it:
count.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…
An event handler can be an async function – typically submitting a form (lesson 0.7):
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 serveralert('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>)}
Render an array of names as a list and show how many there are.
CHALLENGE: A list of names 1. Instead of the single hard-coded <li>, render all the names from the `names` array with names.map(…). Every <li> needs a key – here the name itself is enough. 2. Fill in the number of names in the heading: names.length
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…
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.
Medium and hard challenges are a bonus for signed-in readers. Signing up is free – just an e-mail, no password.
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).setX((prev) => …).