Skip to the solution
EasyPhone Screen6 min read

What are React synthetic events?

1.2k views

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.

  • events
  • architecture

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 8 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactInspecting the synthetic event, its native counterpart, and life after an await
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>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 1 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextEasy

What are React Hooks? Name a few common ones.

Continue