Interactivity is what turns a static page into an application, and in React that means attaching event handlers declaratively to your JSX. This lesson covers camelCase event naming, the cross-browser SyntheticEvent object, preventing default browser behavior, stopping propagation, and reading values out of form inputs.
1Declarative Interactivity
Interactivity is what separates a modern web application from a static document, and in React you handle user actions ā clicks, hovers, keystrokes, form submissions ā through its event system. Rather than gluing generic <script> tags onto markup, you attach the function that should run directly on the JSX element it belongs to.
Something like <button onClick={handleClick}> keeps the UI structure and the behavior that responds to it defined in exactly the same place, which is what makes React's approach declarative.
// React Events: Handling User InputReact Component Preview
2camelCase Syntax
React event names must be written in camelCase ā onClick, onMouseOver, onChange ā rather than the all-lowercase onclick that plain HTML uses; the lowercase version triggers a console warning and is silently ignored.
You also assign the actual function reference rather than a string of code, and critically without parentheses (onClick={handleClick}, not onClick={handleClick()}), since calling it would run the function immediately during render instead of waiting for the click.
function MyButton() {
const handleClick = () => console.log('Clicked!');
return <button onClick={handleClick}>Click Me</button>;
}React Component Preview
3SyntheticEvent Abstraction
Whenever an event fires, React hands your handler a SyntheticEvent rather than the browser's raw native event object. This is a cross-browser wrapper that guarantees the event behaves identically whether the user is on Chrome, Firefox, Safari, or Edge.
It still exposes the same familiar interface native events provide, including e.target, e.type, e.preventDefault(), and e.stopPropagation(), so you rarely need to think about which browser is running your code.
function logEvent(e) {
console.log(e.type); // 'click'
console.log(e.target); // The button element
}React Component Preview
4e.preventDefault() is Crucial
Elements like <form> have default browser behavior built in ā submitting one reloads the entire page, which in a React single-page app would wipe out all of your component state in an instant.
Calling e.preventDefault() inside the submit handler stops that native reload from happening, letting your own submission logic ā like sending data to an API ā run without losing anything the user had on screen.
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted without reload!');
};React Component Preview
5Stopping Bubbling
DOM events bubble upward: clicking a button nested inside a <div> fires the button's onClick first, then the div's onClick immediately after ā which causes real bugs if the div opens a modal while the button is meant to delete an item.
Calling e.stopPropagation() inside the inner handler stops the event from continuing to bubble up, so the parent's handler never fires for that click.
const handleChildClick = (e) => {
e.stopPropagation();
console.log('Child clicked only!');
};React Component Preview
6Text Fields and onChange
Text inputs use onChange rather than onClick, and it fires on every single keystroke, not just once when the field loses focus. The value the user just typed lives on e.target.value.
Reading that value inside the handler is the fundamental mechanism behind Controlled Components, where a React state variable drives exactly what the input currently displays.
<input onChange={(e) => console.log(e.target.value)} />React Component Preview
7Wrapping with Inline Arrows
To pass extra data to a handler, like the ID of the item a button should delete, you can't call the function directly in JSX ā onClick={deleteUser(id)} would run it immediately during render, not on click.
Instead, wrap it in an inline arrow function, onClick={() => deleteUser(id)}, so the call to deleteUser only actually happens once the click event occurs.
<button onClick={() => deleteUser(user.id)}>Delete</button>React Component Preview
8Putting it all together
Putting it together: React events are always camelCase, handlers receive a cross-browser SyntheticEvent object, e.preventDefault() stops unwanted native behavior like form reloads, and e.stopPropagation() stops an event from bubbling up to parent elements.
Once the logic inside a handler grows beyond a line or two, it's worth extracting it into a named function defined above your component's return statement, keeping the JSX itself focused purely on structure.
<h1>Events: Secured</h1>React Component Preview
9Final Check
A quick gut-check on the essentials covered so far: the value typed into an input is read from e.target.value inside an onChange handler, not from some other shorthand or a separate callback argument.
That exact path is worth memorizing, since it's the mechanism every controlled input in React depends on, whether it's a simple search box or a full multi-field form.
e.target.valueReact Component Preview
10Moving Forward
With event handling covered ā attaching handlers declaratively, reading the SyntheticEvent, preventing default behavior, and stopping propagation ā the next step is connecting these events to component memory.
React State, via useState, is what actually lets an event handler change what's on screen, turning a click or keystroke into a visible update rather than just a logged message.
/* Ready for State */React Component Preview
11Step-by-Step Breakdown
Interactivity with Events. Welcome to React Events. Interactivity is what separates a modern web application from a static document. In React, we handle user actionsālike clicks, hovers, keyboard typing, and form submissionsāusing Events. React's event system is designed to be highly declarative, allowing you to attach JavaScript functions directly to your JSX elements.
camelCase Naming. The first major difference between React events and standard HTML events is naming. In pure HTML, event names are entirely lowercase (e.g., onclick, onmouseover). In React, all events MUST be written in camelCase (e.g., onClick, onMouseOver, onChange). If you use the lowercase version, React will throw a console warning and ignore the event completely.
Functions, Not Strings. The second major difference is how you assign the handler. In legacy HTML, you pass a string of JavaScript code (onclick="doSomething()"). In React, you pass the actual JavaScript function reference using curly braces (onClick={doSomething}). DO NOT add parentheses at the end (doSomething()), or the function will execute instantly when the component renders, instead of waiting for the click!
Which of the following is the correct way to attach a saveData function to a button click in React?
- ā<button onclick="saveData()">
- ā<button onClick={saveData()}>
- ā<button onClick={saveData}>
SyntheticEvents. When an event triggers, React passes an 'event object' (usually denoted as e or event) to your handler function. This isn't the raw browser event. It is a React 'SyntheticEvent'. It's a cross-browser wrapper that ensures the event object behaves exactly the same way whether the user is on Chrome, Firefox, Safari, or Edge. It has the same interface as native events, including e.stopPropagation() and e.preventDefault().
Preventing Default. Certain HTML elements have default behaviors built into the browser. The most common is the <form> element, which will automatically refresh the entire page when a <button type="submit"> is clicked. In a React Single Page Application (SPA), a page refresh wipes out all your state! You must call e.preventDefault() inside your handler to stop the browser from reloading.
If you have a form that triggers a full page refresh when submitted, wiping out all your React state, what method did you forget to call?
- āe.stopPropagation()
- āe.preventDefault()
Stopping Propagation. Events in the DOM 'bubble' upwards. If you click a <button> that is inside a <div>, the button's onClick fires first, but then the div's onClick will fire immediately after! This can cause severe bugs if the div handles navigation and the button handles deletion. To prevent an event from bubbling up to parents, use e.stopPropagation().
Form Inputs (onChange). While buttons use onClick, text inputs use onChange. The onChange event fires *every single time* a user types a keystroke. To capture what they actually typed, you access e.target.value. This is the fundamental mechanism for building Controlled Components (where React state drives the input value).
If you have an onChange event triggered by an <input>, how do you access the string the user just typed?
- āe.text
- āe.data
- āe.target.value
Passing Arguments. Often, you need to pass extra data to an event handler, like the ID of the specific item you want to delete. Because you cannot execute the function directly in the JSX (e.g., onClick={deleteUser(id)} is wrong), you must wrap it in an inline arrow function: onClick={() => deleteUser(id)}.
Arguments + Event Object. What if you need to pass a custom argument AND you still need access to the SyntheticEvent object (for example, to stop propagation)? You simply pass e into the arrow function, and then hand it down to your handler alongside your custom arguments.
If you need to call updateItem(itemId) on click, AND call e.preventDefault(), which syntax is correct?
- āonClick={updateItem(itemId, e)}
- āonClick={(e) => { e.preventDefault(); updateItem(itemId); }}
Separation of Handlers. While inline arrow functions are convenient, putting massive blocks of logic inside your JSX makes the component unreadable. Best practice dictates that complex logic should be extracted into a named function defined above your return statement. Keep the JSX purely focused on the UI structure.
Mastery Achieved. Interactivity mastered! You understand camelCase naming, passing function references, the SyntheticEvent system, stopping propagation, and handling inputs. You are now ready to tie these events to dynamic variables using React State (useState) to build truly reactive applications.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Interactive Elements Should Be Real Buttons and Links, Not Divs With onClick
Attaching an `onClick` handler to a `<div>` or `<span>` gives you a click listener but none of the built-in keyboard support, focus ring, or `role` semantics a `<button>` provides for free ā use real interactive elements so keyboard and screen reader users can actually trigger the handler.
2Keyboard Users Need Equivalent Handlers to Mouse Users
An element that only responds to `onClick` is invisible to someone navigating by keyboard; make sure custom interactive components also handle `onKeyDown` for Enter/Space, or better yet, use a native element that supports both interactions automatically.
SEO Implications
- 1
Content Gated Entirely Behind a Click Handler Is Invisible to Crawlers
If important text only renders after a user clicks something (an accordion, a 'read more' handler that fetches content), a crawler that doesn't execute that interaction may never see it ā render essential content directly in the markup rather than requiring an event to reveal it.
- 2
preventDefault on Navigation Links Can Break Crawlability
Calling `e.preventDefault()` on an `<a href>` to handle navigation manually is fine for client-side routing, but the `href` itself should still point to a real, crawlable URL so search engines can discover and index the linked page independent of JavaScript execution.
Best Practices
Never Call a Handler Function Directly Inside JSX
`onClick={handleClick}` passes a reference that React calls later, on click; `onClick={handleClick()}` calls it immediately during render. This mistake either breaks the UI or fires the handler on every single render instead of on user interaction.
Reach for e.stopPropagation() Only When Bubbling Actually Causes a Problem
Stopping propagation by default can silently break other handlers a parent legitimately relies on, like a modal's outside-click-to-close listener; use it deliberately, only on the specific handler where bubbling causes an actual conflict.
Frequent Bugs
A form handler that calls fetch or updates state seems to run, but the page reloads and all state resets anyway.
The submit handler is missing `e.preventDefault()`, so the browser's native form submission still triggers a full page reload alongside your handler's logic. Add `e.preventDefault()` as the first line of the handler.
Clicking a delete button inside a clickable card also triggers the card's own onClick handler (e.g., opening a detail view right as the item is deleted).
The click event bubbles from the button up to the card's div. Call `e.stopPropagation()` inside the button's own handler so the card's onClick never receives that particular click.
Real-World Examples
Deletable List Item Without Triggering Parent Navigation
A list of cards navigates to a detail page when clicked, but each card also has a delete button in the corner ā the delete button's handler calls `e.stopPropagation()` so clicking it removes the item instead of also triggering the card's navigation.
<div onClick={() => navigate(`/items/${item.id}`)}>
<p>{item.name}</p>
<button onClick={(e) => {
e.stopPropagation();
deleteItem(item.id);
}}>Delete</button>
</div>