Skip to solution
easyPhone Screen

What are React synthetic events?

1.2k views
01

Understand the problem

Question presented to candidate: "When you write onClick={e => ...}, what is that e?"

What a strong answer should cover:

  • It is not the browser's native event. It is a React wrapper — SyntheticBaseEvent — with the same interface as the DOM event: type, target, currentTarget, preventDefault(), stopPropagation().
  • The native event is still available at e.nativeEvent whenever you need something React does not surface.
  • Its original purpose was cross-browser normalisation — one consistent shape and one consistent set of property names across browsers that disagreed.
  • Event pooling was removed in React 17. Before that, React reused one object and nulled its fields after the handler, so reading e.target after an await gave null and you needed e.persist(). That is now historical.
  • preventDefault and stopPropagation on the synthetic event do reach the native event.
  • Names are camelCased (onClick, onChange) and a few behaviours are normalised — notably onChange, which fires on every keystroke rather than on blur like the DOM change event.

Clarifying questions expected:

  • "Do you need the native event for something specific?" — that decides whether e.nativeEvent comes up.
  • "Which React version?" — pooling is the one answer that changed.

Code / implementation expected: No. This is definitional; a one-line snippet is plenty.

eventsarchitecture
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — no prior knowledge of the event system assumed. Difficulty: Easy

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Everything about the event object below was **executed again

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

Inspecting the synthetic event, its native counterpart, and life after an await
Run Playground
import { useState } from "react";

export default function App() {
  const [lines, setLines] = useState([]);
  const say = (s) => setLines((l) => [...l, s]);

  const inspect = (e) => {
    say("synthetic:   " + e.constructor.name);
    say("nativeEvent: " + e.nativeEvent.constructor.name);
    say("same object? " + (e === e.nativeEvent));
    say("type:        " + e.type);
    say("target:      " + e.target.tagName + "  currentTarget: " + e.currentTarget.tagName);
  };

  // Pre-React-17 this needed e.persist(); the fields were nulled the moment
  // the handler returned. Since 17 there is no pooling, so this just works.
  const afterAwait = async (e) => {
    await new Promise((r) => setTimeout(r, 300));
    try {
      say("300ms later: type=" + e.type + " target=" + e.target.tagName);
    } catch (err) {
      say("300ms later: threw — " + err.message);
    }
  };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.7, maxWidth: 540 }}>
      <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
        <button onClick={inspect}>inspect the event</button>
        <button onClick={afterAwait}>read it 300ms later</button>
        {/* target is the span, currentTarget is the button */}
        <button onClick={(e) =>
          say("nested: target=" + e.target.tagName + " currentTarget=" + e.currentTarget.tagName)}>
          <span>click the inner span</span>
        </button>
        <button onClick={() => setLines([])}>clear</button>
      </div>

      <pre style={{ background: "#f6f6f8", padding: 12, borderRadius: 8, fontSize: 12, marginTop: 12, minHeight: 120 }}>
{lines.length ? lines.join("\n") : "press a button"}
      </pre>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 1 of 119 decoded in the React.js track. One more won't hurt.

Back to track