B6 · Part 1 · Beginner
Events & conditional rendering
Pass the function, do not call it — and know why && puts a zero on your page.
09On this page
- Handlers are props holding functions
- The event object
- Where events actually listen
- Passing arguments without recreating the world
- Keyboard, focus and the events worth knowing
- Conditional rendering: four ways
- stopPropagation, and when it is the wrong tool
- The four states every screen has
- Conditional attributes
Handlers are props holding functions
<button onClick={handleClick}>Save</button> // ✓ passes the function
<button onClick={handleClick()}>Save</button> // ✗ calls it during render
<button onClick={() => remove(id)}>Delete</button> // ✓ wrap when you need an argument
The second line is the most common beginner bug in React. It runs
handleClick while rendering and hands React whatever it returned
— usually undefined. If the handler sets state, you get an
infinite render loop.
The event object
Handlers receive a SyntheticEvent — React's wrapper with the same
API as the native one, normalised across browsers. The real event is at
e.nativeEvent if you ever need it.
function handleSubmit(e) {
e.preventDefault(); // stop the page reloading
e.stopPropagation(); // stop it bubbling to parents
console.log(e.target.value, e.currentTarget);
}
e.target is what was actually clicked, which may be a child.
e.currentTarget is the element the handler is attached to. Use
currentTarget when you want the thing you bound to.
Returning false from a handler does nothing in React — that
is a jQuery habit. Call preventDefault().
Where events actually listen
React does not attach a listener to each button. It attaches one listener per
event type at the root of your app and works out which component it belongs
to when the event bubbles up. That is why handlers are cheap even on a
thousand rows, and why a stray stopPropagation on a native
listener can stop a React handler firing.
Passing arguments without recreating the world
{rows.map((row) => (
<Row key={row.id} onSelect={() => select(row.id)} /> // fine
))}
An arrow in JSX creates a new function every render. On a normal list that is irrelevant — making a closure is cheap. It only matters when the child is memoised, because a new function is a new prop and defeats the memo; that is memoisation's problem, not something to pre-optimise here.
The alternative, when you want one stable handler for a whole list:
function handleClick(e) {
const id = e.currentTarget.dataset.id; // read it off the DOM
select(id);
}
<button data-id={row.id} onClick={handleClick}>
Keyboard, focus and the events worth knowing
<input
onKeyDown={(e) => { if (e.key === "Enter") submit(); }}
onFocus={...}
onBlur={...}
/>
<div onClickCapture={...}> // fires on the way down, before children
e.key is the modern property — "Enter",
"Escape", "ArrowDown". keyCode is
deprecated. Every React event also has a Capture variant that
runs during the capture phase, which is occasionally the only way to
intercept something before a child handles it.
Event pooling — where the event object was reused and reading it
asynchronously gave you nulls — was removed in React 17. If you find
advice about calling e.persist(), it no longer applies.
Conditional rendering: four ways
Ternary, when there are two outcomes
<div>{isLoggedIn ? <Dashboard /> : <Login />}</div>
&&, when there is one
{error && <p className="error">{error}</p>}
The zero trap
{items.length && <List items={items} />}
With an empty list this is 0 && ..., which evaluates to
0 — and React renders 0 as visible text. A
stray zero appears on the page and nobody can find where it came from.
{items.length > 0 && <List items={items} />} // ✓ force a boolean
{items.length ? <List items={items} /> : null} // ✓ or a ternary
Early return, when the whole component branches
function Profile({ user }) {
if (!user) return <Spinner />;
if (user.banned) return <Banned />;
return <div>{user.name}</div>; // the happy path, unindented
}
This is usually the most readable option once you have more than two branches, and it keeps the main case at the left margin.
A lookup object, when there are many
const views = {
loading: <Spinner />,
error: <ErrorPanel />,
empty: <Empty />,
ready: <List items={items} />,
};
return views[status] ?? null;
Cleaner than a chain of ternaries, and it fails loudly if you add a status and forget the view. Note that every branch is constructed here, so keep the elements cheap or use a function per key.
stopPropagation, and when it is the wrong tool
<div onClick={closeMenu}>
<div className="menu" onClick={(e) => e.stopPropagation()}> // ✗ fragile
...
</div>
</div>
The click-outside-to-close pattern written with stopPropagation
silently breaks anything else listening further up — analytics, a
parent's keyboard handling, a library's outside-click detection. Check what
was clicked instead:
function handleClick(e) {
if (menuRef.current?.contains(e.target)) return; // inside — ignore
closeMenu();
}
preventDefault and stopPropagation do different
things: the first cancels the browser's default action (submitting,
navigating, checking a box), the second stops the event travelling. Reaching
for the wrong one is a common source of "the form still reloads".
The four states every screen has
Beginner React tends to render only the happy path. A screen that touches data has four, and the last two are the ones users actually hit.
| State | What the user sees |
|---|---|
| Loading | A skeleton or spinner — and ideally the shape of what is coming |
| Error | What went wrong and what to do, not "Something went wrong" |
| Empty | Why it is empty and how to fill it, not a blank box |
| Ready | The data |
Interviewers watching you build a screen are usually counting these. Handling the empty state unprompted is one of the cheapest signals you can give.
Conditional attributes
<button
className={"btn " + (active ? "is-active" : "")}
disabled={isSaving}
aria-pressed={active}
>
disabled={false} removes the attribute entirely — React
understands boolean DOM attributes, so you never need
{isSaving ? "disabled" : ""}.
Interview answer, one sentence
"Handlers are props holding function references, so you pass the function
rather than calling it, and React delegates the real listeners to the root.
For conditionals, && is fine as long as the left side is
a boolean — with a number, zero renders."
Opens in the editor: write it, run it, and check it against real tests.
All 46 for this level →Finished reading?
0 of 57 chapters read in this series