At the close of the previous module we left off with a confession: CicloUrbano's catalogue is a shop window. Data arrives, cards paint themselves with their colour and status badge, but the app doesn't react to anything the person using it does. What's missing is the wire that connects the interface to the logic, and that wire is events. In this lesson you'll learn how React connects a click, a keystroke or a focus change to a function of yours: the exact syntax, the classic mistake of calling the handler instead of passing it, what a synthetic event is and what API it offers, how to pass arguments, how bubbling and capturing work, and how to name handlers so the code is still readable six months from now. By the end, TypeSelector will stop being a mock-up and BikeCard will let its parent know which bike was clicked.
Contents
- What an event handler is and where it lives
onClickversusonclick: the real differences- Pass the function, don't call it
- The synthetic event object
preventDefaultandstopPropagation- Handlers with arguments: inline arrow functions
- Bubbling and capturing
- Catalogue of common events
- Where to define the handler and how to name it
- CicloUrbano: a selector that responds and a card that notifies
- What an event handler is and where it lives
An event handler is an ordinary function that React runs when something happens in the interface: a click, a keystroke, a form submission. There's nothing special about it: it's a plain JavaScript function declared inside your component's body.
// src/components/TestButton.jsx
function TestButton() {
// 1. Declare a function inside the component
function handleClick() {
console.log('The button was clicked');
}
// 2. Hand it to React through a prop on the element
return <button onClick={handleClick}>Click here</button>;
}
export default TestButton;Three details worth fixing in your mind from minute one:
- The function is declared inside the component, not outside. That way it can read that specific instance's props and state, which is exactly what it'll need as soon as it does something useful.
onClickis a prop, not an HTML attribute. It's written in camelCase, likeclassNameortabIndex, because JSX uses property names, not attribute names.- The value goes inside curly braces, because it's a JavaScript expression: the reference to the function. In quotes it would be a plain string, and React would warn you with an error.
This pattern — declare the function above, reference it below — is what you'll see in 95% of the React code you come across.
onClick versus onclick: the real differences
onClick versus onclick: the real differencesIf you come from framework-free JavaScript you know two ways of listening for a click: the onclick="..." HTML attribute and element.addEventListener('click', fn) in the script. React replaces both with a third.
| Aspect | HTML onclick="..." |
DOM addEventListener |
React onClick={...} |
|---|---|---|---|
| Spelling | All lowercase | Lowercase name, as a string | camelCase: onClick, onMouseEnter |
| Value | A string of code | A function reference | A function reference |
| Where it's written | In the markup | In the JavaScript, looking up the node | In the markup, but it's JavaScript |
| How many listeners | One; the second overwrites the first | As many as you want | One per prop; for several, a function that calls several |
| Needs removing | No | Yes, with removeEventListener, or it leaks memory |
No: React does it on unmount |
| Variable scope | Global (window) |
The closure where it's defined | The component's: props and state at hand |
| Needs to look up the node | No | Yes (getElementById, querySelector) |
No: the handler travels with the element |
| Event object | Native | Native | Synthetic (a wrapper, see section 4) |
Look at the two most important rows. The first is that in React the value is a function, not a string of code: there's no evaluating text, the editor autocompletes, and syntax errors are caught at compile time. The second is that you don't have to remove the listener: when the component disappears from the screen, React cleans up for you. With manual addEventListener, forgetting the matching removeEventListener is one of the most common sources of memory leaks.
One technical nuance worth knowing, even though it doesn't change how you write code: React does not attach a listener to every <button>. It attaches a handful of listeners to the app's root node (the <div id="root"> you saw in lesson 01-03) and, when an event arrives, decides which of your handlers should run. This technique is called event delegation, and it's what lets a thousand buttons on screen cost far fewer than a thousand listeners.
flowchart LR
A["Click in the browser"] --> B["Single listener<br/>at React's root"]
B --> C["React identifies the<br/>element in the tree"]
C --> D["Runs your function<br/>handleClick(event)"]
- Pass the function, don't call it
This is the most common mistake among people starting out with events in React, and it earns its own section because the symptom is so misleading.
// CORRECT: we pass the reference to the function. React will call it when it's due
<button onClick={handleClick}>Book</button>
// WRONG: we call it ourselves during render
<button onClick={handleClick()}>Book</button>What exactly happens in the second line? Remember that JSX is evaluated while rendering. The parentheses run the function right then, and what gets assigned to onClick isn't the function, but its return value. Since handleClick doesn't return anything, onClick ends up being undefined.
The consequences, in order of appearance:
- The
console.logmessage shows up the moment the page loads, without anyone having clicked anything. - Clicking the button does absolutely nothing, because
onClickisundefined. - If the handler updates state, it triggers a new render, which calls the function again, which updates state again… an infinite loop, and the tab freezes with a "Too many re-renders" error.
The mnemonic rule is simple: with parentheses, now; without parentheses, later.
onClick={handleClick} // later (when clicked) ✔
onClick={handleClick()} // now (during render) ✘
onClick={() => handleClick()} // later, wrapped in another function ✔The third form is valid, and you'll need it in section 6, once you have to pass arguments. But if the handler takes no arguments, write the first form: it's shorter and more direct.
- The synthetic event object
When React runs your handler, it passes it one argument: the event object.
function handleClick(event) {
console.log(event.type); // "click"
console.log(event.target); // the DOM node that received the click
console.log(event.currentTarget); // the node where the handler is attached
}It isn't the browser's native event: it's a synthetic event (SyntheticEvent), an object React builds by wrapping the native one.
Why it exists
In the years React was born, every browser exposed events in slightly different ways: different property names, incompatible behaviour, a preventDefault that some browsers called something else entirely. The synthetic event solves three things:
| Reason | What it delivers |
|---|---|
| Normalization | The same API across every browser; your code carries no browser-specific branches |
| Compatibility with delegation | React can reconstruct the event's path through the component tree, which doesn't always match the DOM tree |
| Stable interface | React guarantees which properties exist, no matter how the browser changes |
The synthetic API mirrors the native one, so everything you already know about DOM events still holds. The properties and methods you'll use daily:
| Member | What it is |
|---|---|
event.target |
The element where the event originated (the deepest one) |
event.currentTarget |
The element the running handler is attached to |
event.type |
The event type as a string: "click", "submit", "keydown" |
event.preventDefault() |
Cancels the browser's default action |
event.stopPropagation() |
Stops the event from bubbling further up the tree |
event.key |
On keyboard events, the key that was pressed: "Enter", "Escape", "a" |
event.nativeEvent |
The original native event, in case you need something the synthetic one doesn't expose |
The distinction between target and currentTarget matters. If a <button> contains a <span> and you click on the text, event.target will be the <span>, while event.currentTarget will be the <button>. Whenever you want to identify "the element I attached the handler to", always use currentTarget.
function IconButton() {
function handleClick(event) {
console.log(event.target.tagName); // "SPAN" if you click the text
console.log(event.currentTarget.tagName); // "BUTTON" always
}
return (
<button onClick={handleClick}>
<span>Book</span>
</button>
);
}A useful bit of history: up to React 16, synthetic events were pooled and reused: as soon as the handler finished, its properties were wiped, and reading them later required calling
event.persist(). That disappeared as of React 17. If you findevent.persist()in a tutorial, it's old code: in React 19 it isn't needed and does nothing.
preventDefault and stopPropagation
preventDefault and stopPropagationThese are two distinct methods that get confused constantly. They do things that have nothing to do with each other.
preventDefault(): cancelling what the browser would do
Some elements have a native behaviour attached: an <a href> navigates, a <form> submits and reloads the page, an <input type="checkbox"> toggles. preventDefault() cancels that behaviour.
function StationLink() {
function handleClick(event) {
event.preventDefault(); // the browser does NOT navigate to /stations
console.log('Show the detail without changing page');
}
return (
<a href="/stations" onClick={handleClick}>
View stations
</a>
);
}There's an important nuance in React: returning false from the handler cancels nothing. In classic HTML, onclick="return false" did the trick. In React the return value is ignored entirely: you have to call event.preventDefault() explicitly.
Its most frequent use, by far, is submitting forms, which you'll see in depth in Forms and Controlled Components.
stopPropagation(): cutting off the event's climb
Stops the event from bubbling further up towards ancestor elements. It's the tool for the "I have a button inside a clickable card and I don't want clicking the button to also select the card" case.
function CardWithButton() {
function handleCardClick() {
console.log('The card was selected');
}
function handleButtonClick(event) {
event.stopPropagation(); // without this, handleCardClick would also fire
console.log('The book button was clicked');
}
return (
<article onClick={handleCardClick}>
<h3>Classic Urban</h3>
<button onClick={handleButtonClick}>Book</button>
</article>
);
}| Method | What it cancels | Typical example |
|---|---|---|
preventDefault() |
The browser's default action | A <form> reloading the page |
stopPropagation() |
The event's climb to its ancestors | A button inside a clickable card |
The two are independent: you can call one, the other, both, or neither.
- Handlers with arguments: inline arrow functions
Very often the handler needs to know what was acted on: which bike type, which id, which row. And there it collides with section 3's rule: you can't write onClick={selectType('urbana')}, because that would run it during render.
The idiomatic solution is to wrap it in an inline arrow function:
Step by step, here's what happens:
() => selectType('urbana')creates a new function that doesn't run yet.- That function is what gets assigned to
onClick. - When someone clicks, React calls that function, and only then does
selectType('urbana')run.
If you also need the event object, declare it as the arrow's parameter and pass it along:
The alternative: a function that returns a function
There's an equivalent, slightly more sophisticated pattern, called currying:
function TypeButtons() {
// Takes the type and RETURNS the handler, already prepared
const onSelectType = (type) => (event) => {
console.log('Type chosen:', type, 'from', event.currentTarget.textContent);
};
return (
<>
<button onClick={onSelectType('urbana')}>Urban</button>
<button onClick={onSelectType('electrica')}>Electric</button>
</>
);
}Here onSelectType('urbana') does run during render, but that's fine: what it returns is another function, and that's what gets assigned to onClick. It's correct, though for beginners the inline arrow is easier to read.
The cost of creating the function on every render
Every time the component renders, the expression () => selectType('urbana') produces a new function. It's a different object from the one before, even though it does exactly the same thing.
Is that a problem? In the vast majority of cases, no. Creating a function in JavaScript is an extremely cheap operation, and the readability you gain more than makes up for it. It only starts to matter when that function is passed as a prop to a memoized component, because then the new reference breaks the memoization and causes unnecessary renders.
That scenario, how to measure it and how to fix it with useCallback, is material for Module 8, specifically The useMemo and useCallback Hooks. For now, keep the practical rule: write inline arrows without fear, and only optimize once you measure a real problem.
- Bubbling and capturing
When you click a nested element, the event doesn't only affect that element: it travels through the tree in two phases.
- Capture phase: the event descends from the root down to the clicked element.
- Bubble phase (bubbling): the event ascends from the clicked element back up to the root.
React lets you listen in both phases. By default, onClick listens during bubbling. Adding the Capture suffix — onClickCapture, onFocusCapture, onKeyDownCapture — makes you listen during capture.
flowchart TD
subgraph CAPTURE["Phase 1: capture (outside to inside)"]
direction TB
C1["main<br/>onClickCapture"] --> C2["article.card<br/>onClickCapture"] --> C3["button<br/>onClickCapture"]
end
C3 --> OBJ["Clicked element:<br/>button"]
OBJ --> B1
subgraph BUBBLING["Phase 2: bubbling (inside to outside)"]
direction TB
B1["button<br/>onClick"] --> B2["article.card<br/>onClick"] --> B3["main<br/>onClick"]
end
Let's see it with an example that prints the full path:
function PropagationDemo() {
return (
<main
onClickCapture={() => console.log('1. main - capture')}
onClick={() => console.log('6. main - bubble')}
>
<article
onClickCapture={() => console.log('2. article - capture')}
onClick={() => console.log('5. article - bubble')}
>
<button
onClickCapture={() => console.log('3. button - capture')}
onClick={() => console.log('4. button - bubble')}
>
Click
</button>
</article>
</main>
);
}Clicking the button, the console prints all six messages in exactly that order: 1, 2, 3, 4, 5, 6.
What each phase is for
| Phase | When it's used |
|---|---|
Bubbling (onClick) |
99% of cases. It's the natural behaviour you expect |
| Bubbling on the parent | A single handler on the container for many children, identifying the source with event.target |
Capture (onClickCapture) |
Intercepting before the children: analytics logging, closing a dropdown menu, blocking interaction in an area |
One important detail: stopPropagation() cuts the path from wherever it's called. If you call it in article's capture phase, the button won't even get to receive its own click. Use it carefully: cutting propagation can break functionality in other components that relied on receiving that event.
- Catalogue of common events
React supports practically every DOM event, always in camelCase. These are the ones you'll use constantly:
| Prop | When it fires | Practical notes |
|---|---|---|
onClick |
On click (mouse, or Enter/Space on a <button>) |
The most used. On a real <button> it also works with the keyboard |
onChange |
When a field's value changes | In React it fires on every keystroke, not on blur like the native DOM |
onInput |
On every text input | In React it's almost identical to onChange; use onChange by convention |
onSubmit |
When a <form> is submitted |
Goes on the form, not the button. Almost always paired with preventDefault() |
onFocus |
When a field receives focus | Useful for highlighting or showing contextual help |
onBlur |
When focus is lost | The classic moment to validate a field (you'll see it in 03-05) |
onKeyDown |
When a key is pressed | Read with event.key. It's what lets you react to Enter or Escape |
onKeyUp |
When a key is released | Less common; for shortcuts, prefer onKeyDown |
onMouseEnter |
When the mouse enters the element | Doesn't bubble: ideal for single-element effects |
onMouseLeave |
When the mouse leaves | The natural pair of the previous one |
onDoubleClick |
On double-click | Watch accessibility: there's no keyboard equivalent |
onContextMenu |
When the context menu opens | Requires preventDefault() to replace it |
onScroll |
When a container is scrolled | Fires a lot; it's worth throttling its frequency |
Two warnings about this table:
- React's
onChangeisn't the DOM'schange. The native one only fires when the field loses focus after having changed; React's fires on every keystroke. This difference is what makes 03-04's controlled components possible. onMouseEnterandonMouseLeavedon't bubble, unlikeonMouseOverandonMouseOut. That's why they're recommended for visual effects: they don't fire when passing over the children.
- Where to define the handler and how to name it
With three handlers the code is still readable; with fifteen, naming discipline is the difference between a maintainable component and a puzzle.
The course's convention
| Case | Convention | Example |
|---|---|---|
| Function defined inside the component | handle prefix + what happens |
handleClick, handleSubmit, handleTypeChange |
| Function prop a component receives | on prefix |
onSelect, onBook, onTypeChange |
| Handler for a DOM element | The native prop, don't invent one | onClick, onSubmit, onKeyDown |
The underlying idea is symmetry: the child declares what can happen to it (onSelect) and the parent decides what to do when it happens (handleSelection).
// The parent defines the function and passes it down
function Catalogue() {
function handleSelection(bike) {
console.log('Bike selected:', bike.model);
}
return <BikeCard bike={firstBike} onSelect={handleSelection} />;
}
// The child only knows it must notify; it doesn't know what happens next
function BikeCard({ bike, onSelect }) {
return <article onClick={() => onSelect(bike)}>…</article>;
}This pattern is called lifting the handler, and it's the basis of child-to-parent communication in React: props carry data down, function props carry notifications up.
When to define the function outside the JSX
| Situation | Recommendation |
|---|---|
| The handler is one line and takes no arguments | Named function above: onClick={handleClick} |
| The handler needs an argument from context | Inline arrow: onClick={() => handleClick(type)} |
| The handler is more than two or three lines | Always a named function above: JSX should read like markup |
| The handler doesn't depend on the component | Helper function in src/utils/, imported |
The rule that sums it all up: JSX describes the interface, not the logic. If an onClick contains an if, a try, or three statements, that logic belongs in a named function.
- CicloUrbano: a selector that responds and a card that notifies
Let's apply all of this to the app. Two concrete changes.
TypeSelector with real handlers
The 02-04 version already used onClick, but with the logic written directly in the JSX. Now that we know how to name and organize handlers, it looks like this:
// src/components/TypeSelector.jsx
import { useState } from 'react';
import { cx } from '../utils/classNames.js';
import styles from './TypeSelector.module.css';
const TYPES = ['todos', 'urbana', 'electrica', 'carga'];
const LABELS = {
todos: 'All',
urbana: 'Urban',
electrica: 'Electric',
carga: 'Cargo'
};
/**
* Bike type selector.
* Props:
* - onTypeChange (function, optional): receives the chosen type when it changes
*
* Still keeps the type in its own state; in 04-01 that state moves up to the parent.
*/
function TypeSelector({ onTypeChange }) {
const [chosenType, setChosenType] = useState('todos');
function handleTypeSelect(type) {
setChosenType(type);
if (onTypeChange) {
onTypeChange(type);
}
}
function handleClearKey(event) {
if (event.key === 'Escape') {
handleTypeSelect('todos');
}
}
return (
<div className={styles.selector} onKeyDown={handleClearKey}>
<p className={styles.title}>Filter by type:</p>
{TYPES.map((type) => (
<button
key={type}
type="button"
className={cx(styles.button, type === chosenType && styles.active)}
onClick={() => handleTypeSelect(type)}
>
{LABELS[type]}
</button>
))}
<p className={styles.selection}>
Current selection: <strong>{LABELS[chosenType]}</strong>
</p>
</div>
);
}
export default TypeSelector;What changed and why:
handleTypeSelectis a named function. Before, the logic lived insideonClick; now it does two things (saving the state and notifying the parent), and that no longer fits comfortably in JSX.onClick={() => handleTypeSelect(type)}: an inline arrow because we need to pass each button'stype. Without it, the function would run during render, as you saw in section 3.onTypeChangeis an optional function prop. If the parent passes it, the selector notifies it; if not, the component keeps working. This is the groundwork for 04-01, where the state will move up to the parent and the filter will actually work.type="button"on the buttons. Without it, a<button>inside a<form>defaults totype="submit"and would submit the form when clicked. It's a cheap precaution that avoids a baffling bug.onKeyDownon the container: thanks to bubbling, a single handler covers all four buttons. PressingEscapewhile any of them is focused resets the filter to "All".
BikeCard notifies which bike was clicked
// src/components/BikeCard.jsx
import { cx } from '../utils/classNames.js';
import styles from './BikeCard.module.css';
import StatusBadge from './StatusBadge.jsx';
/**
* Card for a bike in CicloUrbano's catalogue.
* Props:
* - bike (object, required) { id, model, type, status, stationId, pricePerHour }
* - stationName (string, optional, defaults to 'Unknown station')
* - onSelect (function, optional): receives the bike object when the card is clicked
* - onBook (function, optional): receives the bike object when "Book" is clicked
*/
function BikeCard({
bike,
stationName = 'Unknown station',
onSelect,
onBook
}) {
const formattedPrice = bike.pricePerHour.toFixed(2);
const unavailable = bike.status !== 'disponible';
function handleCardClick() {
if (onSelect) {
onSelect(bike);
}
}
function handleBookClick(event) {
// Without this, the click would bubble up to the <article> and select the card too
event.stopPropagation();
if (onBook) {
onBook(bike);
}
}
return (
<article
className={cx(styles.card, styles[bike.type], unavailable && styles.dimmed)}
onClick={handleCardClick}
>
<h3 className={styles.title}>
{bike.model} <StatusBadge status={bike.status} />
</h3>
<p className={styles.meta}>Type: {bike.type}</p>
<p className={styles.meta}>Station: {stationName}</p>
<p className={styles.price}>€{formattedPrice} / hour</p>
<button type="button" className={styles.action} onClick={handleBookClick}>
Book
</button>
</article>
);
}
export default BikeCard;The two key points:
onSelectandonBookare optional props. Theif (onSelect)check avoids the "onSelect is not a function" error when the parent doesn't pass them. An equally valid alternative is giving a default value:onSelect = () => {}.event.stopPropagation()on the button is essential: the button sits inside the<article>, which already listens for clicks, so without cutting the propagation every "Book" click would also trigger the card's selection. This is the canonical use case from section 5.
A note we'll come back to in Accessibility in Interactive Components: putting
onClickon an<article>makes it clickable with the mouse, but not with the keyboard. It's a conscious debt we'll settle before closing the module.
App receives the notifications
// src/App.jsx
import { bikes } from './data/domain.js';
import Header from './components/Header.jsx';
import FleetSummary from './components/FleetSummary.jsx';
import TypeSelector from './components/TypeSelector.jsx';
import BikeList from './components/BikeList.jsx';
import Footer from './components/Footer.jsx';
function App() {
function handleTypeChange(type) {
console.log('Type chosen in the selector:', type);
}
function handleBikeSelect(bike) {
console.log('Selected:', bike.id, bike.model);
}
function handleBooking(bike) {
console.log('Book:', bike.id, 'at', bike.pricePerHour, '€/hour');
}
return (
<>
<Header />
<main>
<FleetSummary fleet={bikes} />
<TypeSelector onTypeChange={handleTypeChange} />
<BikeList
first={bikes[0]}
second={bikes[1]}
third={bikes[2]}
onSelect={handleBikeSelect}
onBook={handleBooking}
/>
</main>
<Footer />
</>
);
}
export default App;BikeList just forwards the two function props to each card. For now App only does console.log: actually storing the selected bike requires the state to live in the parent, and that's exactly the subject of Lifting State Up. What matters here is that the flow already works: the click happens on the card and the information reaches the top.
flowchart TD
APP["App<br/>handleBikeSelect"] -- "onSelect (function prop)" --> LIS["BikeList"]
LIS -- "onSelect" --> TAR["BikeCard"]
TAR -. "notifies on click: onSelect(bike)" .-> APP
Open the browser console, click a card and then its "Book" button: you'll see a different message each time, without them mixing up. CicloUrbano now responds.
Common Mistakes and Tips
- Calling the handler instead of passing it.
onClick={handleClick()}runs it during render. If it also updates state, you get an infinite loop and the "Too many re-renders" error. No parentheses, or wrap it in an arrow. - Writing the event name in lowercase.
onclickisn't a React prop: React treats it as an unknown attribute and warns in the console, suggestingonClick. Always camelCase. - Passing the handler as a string.
onClick="handleClick()"is HTML syntax; in JSX it assigns a string of text and doesn't work. - Expecting
return falseto cancel the default behaviour. In React the return value is ignored: useevent.preventDefault(). - Confusing
preventDefaultwithstopPropagation. The first cancels what the browser would do; the second cancels the event's climb. They aren't interchangeable. - Overusing
stopPropagation(). Cutting propagation "just in case" breaks functionality that relies on bubbling, like closing a menu when clicking outside it. Use it only when you have a real conflict. - Confusing
targetwithcurrentTarget. If the clickable element contains an icon or a<span>,targetwill be that child. To identify the handler's own element, usecurrentTarget. - Forgetting
type="button"on buttons inside a form. The default issubmit, so the button will submit the form and the page will reload. - Using
event.persist(). That's React 16-era code. Since React 17 events are no longer pooled and that call is unnecessary. - Putting
onClickon a<div>or an<article>. It works with the mouse and fails with the keyboard. Use a<button>when you can; if you can't, you'll need to add a role,tabIndexand a keyboard handler, as you'll see in 03-06. - Tip: one handler, one responsibility. If
handleClicksaves state, notifies the parent, and logs analytics, extract helper functions. - Tip: make the most of bubbling. A single
onKeyDownon the container covers all its children. Less code and fewer chances of missing a case. - Tip: check with
console.log(event)when something doesn't add up. The synthetic object is inspectable and usually reveals right away whether the problem is the wrong element or the wrong event.
Exercises
Exercise 1
Analyse this component and identify three event-related bugs. Explain the symptom of each one and write the fixed version.
function StationButton({ station }) {
function handleClick(event) {
console.log('Station:', station.name);
return false; // so the link doesn't navigate
}
return (
<div>
<a href="/stations" onclick={handleClick()}>
{station.name}
</a>
</div>
);
}Exercise 2
Create the BikeRow component (src/components/BikeRow.jsx) as a compact version of the card, meant for a table. It should:
- Receive
bikeand two function props:onSelectandonMarkMaintenance. - Render a
<tr>row with the model, type, status and price, plus a cell with a "Send to workshop" button. - Notify with
onSelect(bike)when any part of the row is clicked. - Notify with
onMarkMaintenance(bike)when the button is clicked, without also triggering the row's selection. - Log to the console, using
event.currentTarget.tagName, which element each handler ran from, to check the difference fromtarget.
Exercise 3
Add fuller keyboard behaviour to TypeSelector, using a single handler on the container:
Escaperesets to type'todos'(already done).- The
ArrowRightkey advances to the next type in theTYPESconstant, andArrowLeftgoes back, both circularly (from the last type back to the first and vice versa). - Any other key should do nothing.
Write only the handler, and explain why it's worth computing the new index from TYPES.indexOf(chosenType).
Solutions
Solution 1.
The three bugs:
| Bug | Symptom |
|---|---|
Lowercase onclick |
React doesn't recognize the prop; the handler never runs, and a console warning suggests onClick |
handleClick() with parentheses |
The function runs during render: the message appears on page load and onClick ends up undefined |
return false |
In React the return value is ignored: the link navigates anyway and the page reloads. You have to call event.preventDefault() |
// src/components/StationButton.jsx
/**
* Link to a station's detail page.
* Props:
* - station (object, required) { id, name, district, docks }
*/
function StationButton({ station }) {
function handleClick(event) {
event.preventDefault(); // cancels the browser's navigation
console.log('Station:', station.name);
}
return (
<div>
<a href="/stations" onClick={handleClick}>
{station.name}
</a>
</div>
);
}
export default StationButton;Solution 2.
// src/components/BikeRow.jsx
import StatusBadge from './StatusBadge.jsx';
/**
* Compact row for a bike, used in table views.
* Props:
* - bike (object, required)
* - onSelect (function, optional): receives the bike when the row is clicked
* - onMarkMaintenance (function, optional): receives the bike when the button is clicked
*/
function BikeRow({ bike, onSelect, onMarkMaintenance }) {
const formattedPrice = bike.pricePerHour.toFixed(2);
function handleRowClick(event) {
console.log('Handler on:', event.currentTarget.tagName); // always "TR"
console.log('Actual source:', event.target.tagName); // "TD", "SPAN"…
if (onSelect) {
onSelect(bike);
}
}
function handleWorkshopClick(event) {
event.stopPropagation(); // stops the click from reaching the <tr>
console.log('Handler on:', event.currentTarget.tagName); // "BUTTON"
if (onMarkMaintenance) {
onMarkMaintenance(bike);
}
}
return (
<tr onClick={handleRowClick}>
<td>{bike.model}</td>
<td>{bike.type}</td>
<td>
<StatusBadge status={bike.status} />
</td>
<td>€{formattedPrice}/h</td>
<td>
<button type="button" onClick={handleWorkshopClick}>
Send to workshop
</button>
</td>
</tr>
);
}
export default BikeRow;Clicking the text of a cell, currentTarget is always TR (where the handler lives), while target is TD. Clicking the button, stopPropagation() stops handleRowClick from running: only the button's message appears in the console.
Solution 3.
function handleKeyDown(event) {
const currentIndex = TYPES.indexOf(chosenType);
if (event.key === 'Escape') {
handleTypeSelect('todos');
return;
}
if (event.key === 'ArrowRight') {
const next = (currentIndex + 1) % TYPES.length;
handleTypeSelect(TYPES[next]);
return;
}
if (event.key === 'ArrowLeft') {
// Adding TYPES.length before the modulo avoids negative indexes
const previous = (currentIndex - 1 + TYPES.length) % TYPES.length;
handleTypeSelect(TYPES[previous]);
}
// Any other key: do nothing, and let the event carry on
}Computing the index from TYPES.indexOf(chosenType), rather than keeping a separate index state, avoids having two sources of truth that could drift apart: the index is a derived value from the state, exactly as you learned in lesson 02-04. And the modulo operator % gives circular behaviour with no extra if; adding TYPES.length before the modulo is necessary because in JavaScript -1 % 4 is -1, not 3.
Conclusion
Events are the point where the interface stops being a picture and becomes an application. You now know that a handler is an ordinary function declared inside the component and handed to React through a camelCase prop; that the prop receives the function, not its call, and why one extra pair of parentheses can cause anything from a premature message to an infinite render loop; that React hands you a synthetic event that normalizes the API across browsers, with target, currentTarget, preventDefault() and stopPropagation() as everyday tools; that passing arguments means wrapping the handler in an inline arrow, whose cost will only matter once you reach Module 8; and that the event travels through the tree in two phases, with onClick listening during bubbling and onClickCapture during capture.
In CicloUrbano that has become two pieces that now respond: a TypeSelector with named handlers, buttons that highlight the current selection, and keyboard shortcuts; and a BikeCard that accepts the onSelect and onBook function props to notify upward of what happens inside it, with stopPropagation() exactly where it was needed. Data flows down through props; notifications flow up through function props. That two-way flow is React's data flow, and it won't change for the rest of the course.
The app reacts now, but it still always paints the same thing. A bike under maintenance shouldn't offer a "Book" button, a catalogue with no results should say so instead of staying blank, and a notice should only appear when there's something to notify. Painting one thing or another depending on state or props is the next step, and it has more subtlety than it looks — including a trap with the && operator that already peeked through in the JSX lesson. You'll see it in Conditional Rendering.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
