skip to content

The SyntheticEvent System

React wraps native DOM events in a normalized SyntheticEvent. You should know how to reach the native event, how to cancel default behaviour and propagation, and the pooling behaviour that older answers still mention but React 17 removed.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a React onSubmit or onClick handler, why does `return false` not stop the browser's default behaviour, and what do you write instead?

level: juniorimportance: must knowfreq 50%

answer

  1. React ignores what a handler returns
  2. the idiom belongs to jQuery and inline attributes
  3. cancel with a method, not a value
  4. two separate calls, not one
  5. e.preventDefault()

basics

~10 s

React ignores a handler's return value entirely. Returning false only cancels defaults in inline HTML attributes and jQuery handlers. In React you call e.preventDefault(), and e.stopPropagation() separately if you also want to stop bubbling.

solid answer

~40 s

React never inspects what a handler returns, so `return false` is dead code — the form still submits and the page still navigates. That idiom comes from two other worlds: an inline HTML attribute like `onclick="return false"`, where the returned value *is* the cancellation protocol, and jQuery, where returning false calls `preventDefault()` **and** `stopPropagation()` for you. In React they are two separate, explicit calls on the SyntheticEvent: `e.preventDefault()` cancels the browser's default action, `e.stopPropagation()` stops the event reaching ancestor handlers, and neither implies the other. The one React 19 case where you do not call preventDefault yourself is passing a function to a form's `action` prop — React then owns the submission rather than letting the browser navigate.

code

javascript · 14 lines
javascript
function SearchForm() {
  function handleSubmit(e) {
    e.preventDefault(); // returning false here would change nothing
    const data = new FormData(e.currentTarget);
    console.log(data.get('q'));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="q" />
      <button type="submit">Search</button>
    </form>
  );
}

go deeper

for a junior

Say plainly that React ignores the return value and that you cancel with e.preventDefault(). Know that stopping bubbling is a separate call.

for a middle

Explain where the return false idiom comes from — inline HTML attributes and jQuery's double shorthand — and state that preventDefault and stopPropagation solve unrelated problems.

for a senior

Bring the cancelable check and the async trap: preventDefault only works during dispatch on a cancelable event, so a call after an await is silently useless. Question whether the element needed a default suppressed at all.

for a principal

Argue the API design: jQuery's overloaded return value coupled two independent concerns and produced quiet bugs, which is exactly why React kept them as explicit separate calls with matching isDefaultPrevented / isPropagationStopped predicates.

## The mechanism React calls your handler and throws the return value away. There is no protocol by which a returned value means anything, so `return false` is a no-op statement that happens to also end the function early. ```js function handleSubmit(e) { console.log('submitting'); return false; // React does not look at this — the page still navigates } ``` To cancel, you call a method on the event: ```js function handleSubmit(e) { e.preventDefault(); // ... your own submit logic } ``` ## Where the myth comes from Two older conventions taught `return false`, and both are real — just not React. The first is the **inline HTML attribute handler**: `<a href="/x" onclick="return false">`. The attribute's body is compiled into a function whose return value the browser genuinely consults; returning false cancels the default action. Note that this is a property of attribute handlers only — a listener added with `addEventListener` ignores its return value too, exactly like React. The second is **jQuery**. jQuery deliberately made `return false` shorthand for calling `preventDefault()` *and* `stopPropagation()`. That double meaning is the reason the idiom spread, and the reason it caused subtle bugs even in jQuery: people who wanted only one of the two effects silently got both. React chose neither convenience. It keeps the two operations separate and explicit. ## preventDefault and stopPropagation are unrelated These answer two different questions: - `e.preventDefault()` — "do not perform the browser's built-in action for this event." A form does not submit and navigate; an anchor does not follow its href; a checkbox does not toggle; a context menu does not open. - `e.stopPropagation()` — "do not let this event continue to handlers on ancestor elements." It has nothing to do with the default action; a link whose click you stopped from propagating still navigates. Calling one when you meant the other is one of the most common React event bugs. A candidate who explains this distinction cleanly has effectively answered the question. React also lets you *ask* about the result afterwards: `e.isDefaultPrevented()` and `e.isPropagationStopped()` report whether an earlier handler already did either. ## Which events even have a default to prevent `preventDefault()` does nothing unless the event is cancelable — `e.cancelable` tells you. Submitting a form, clicking an anchor or a checkbox, dropping a dragged item, and opening a context menu are all cancelable. Many others are not: a `focus` or a `scroll` has no default action you can veto this way. ## The React 19 exception worth knowing If you pass a function to a form's `action` prop, React handles the submission itself and does not let the browser navigate, so there is no `preventDefault()` for you to write: ```js <form action={async (formData) => { await save(formData); }}> ``` The classic `onSubmit={handleSubmit}` form still requires the explicit call. Both are current React 19; the difference is who owns the submission. ## The nuance behind the anchor case A very common variant is "I have `<a href="#">` with an onClick and the page jumps to the top." That jump is the default action of following the fragment, so `e.preventDefault()` is the fix — but the better fix is usually to stop misusing an anchor. If the element performs an action rather than navigating, it should be a `<button type="button">`, which has no navigation default at all and is correct for assistive technology. Reaching for preventDefault to suppress an element's natural behaviour is often a sign the wrong element was chosen.

  • If preventDefault and stopPropagation are separate, which one do you need to stop a link from navigating?
    `preventDefault()`. Navigation is the anchor's default action, so cancelling it is what keeps the page put. `stopPropagation()` only stops ancestor handlers from seeing the click — the browser would still follow the href. Needing preventDefault on an anchor that never navigates is usually a hint the element should have been a `<button type="button">`.
  • How can a handler tell that some other handler already called preventDefault on the same event?
    React's SyntheticEvent exposes `isDefaultPrevented()`, and the DOM-standard `defaultPrevented` property is on there too. Both let a later handler in the dispatch skip work that a component closer to the target already claimed. There is a matching `isPropagationStopped()` for the other operation.
  • Does preventDefault always work? When does the call do nothing?
    Only cancelable events respond to it — `e.cancelable` reports whether this one does. Events such as focus or scroll have no vetoable default, so the call is silently ignored. The same is true if the default has already happened: preventDefault must be called during dispatch, not later from an async callback.

saying these in an interview costs you the question

  • Says return false cancels the default like in jQuery
  • Thinks preventDefault also stops the event bubbling
  • Uses stopPropagation to stop a link navigating
  • Calls preventDefault asynchronously after an await
  • Believes React warns you when a handler returns false

context

open as a page

In React 19, what object does React pass to an event handler such as onClick, and how do you reach the underlying browser event from it?

level: juniorimportance: must knowfreq 65%

basics

~10 s

React passes a SyntheticEvent: a cross-browser wrapper that mirrors the DOM event interface (type, target, preventDefault, stopPropagation). The untouched browser event is always available on e.nativeEvent for anything React does not normalize.

open as a page

What does React's onClickCapture prop do that onClick does not, and when would you reach for it?

level: middleimportance: should knowfreq 35%

basics

~20 s

onClickCapture registers the handler for the capture phase, so it runs on ancestors first, top-down, before the clicked element's own onClick. React offers a Capture variant of every event prop for intercepting an interaction before descendants handle it.

open as a page

Older React guidance says you must call e.persist() before using an event inside setTimeout or after an await. What was event pooling, and what is actually true from React 17 onward?

level: middleimportance: should knowfreq 45%

basics

~20 s

Before React 17, React reused one SyntheticEvent object per event type and nulled its fields once the handler returned, so async reads saw null unless you called e.persist(). React 17 removed pooling; in React 19 the event is an ordinary object and persist() does nothing.

open as a page

You need a property that React's SyntheticEvent does not expose — for example the submitter of a form's submit event. How do you decide when to drop to e.nativeEvent, and what do you take on when you do?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

React normalizes a curated subset of each event's properties; anything outside it lives on e.nativeEvent, which is the browser's own object. Reading it is supported and normal, but you inherit raw browser behaviour, so you own the compatibility check.

open as a page