The Developer's Quarterly · React InternalsVol. III · May 2026

Synthetic Events in React:What They Are and When They Matter

Synthetic events are React's normalized wrapper around browser events, which is why handlers feel familiar while still fitting React's event system.

If you write event handlers in React every day, you are already using synthetic events whether you think about them or not. When you attach onClick, onChange, onSubmit, or onKeyDown to JSX, React does not hand you the raw browser event object directly. It wraps browser events in its own cross-browser interface called SyntheticEvent.

That wrapper solves a practical problem: browser event APIs were historically inconsistent, and React wanted a stable contract for component code. The result is that the event object you receive behaves like a DOM event in the ways you usually need, while still giving React room to normalize behavior across environments.

In modern React, synthetic events are not something you need to fight. They are mostly invisible. But understanding what they are helps when you need to inspect target, currentTarget, nativeEvent, propagation behavior, or old advice about event pooling that still shows up in blog posts and code reviews.

The basic idea

A synthetic event is React's event wrapper. The API is intentionally similar to the native DOM event interface, so handlers feel familiar.

Basic click handler
function SaveButton() { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { console.log(event.type); console.log(event.currentTarget.disabled); }; return <button onClick={handleClick}>Save</button>; }

In that handler, event is not the browser's raw MouseEvent. It is a React synthetic event that exposes a normalized shape with familiar properties and methods such as type, target, currentTarget, preventDefault(), stopPropagation(), and nativeEvent.

Why React uses synthetic events

The main benefit is consistency. React centralizes event handling and normalizes differences so your component code can stay predictable.

1 Cross-browser behavior

React smooths over browser differences so handlers behave more consistently.

2 React-friendly API

The event contract fits component code instead of making every feature deal with low-level DOM details.

3 Centralized delegation

React routes handlers through its event system instead of treating each JSX listener as an isolated native subscription.

target vs currentTarget

One of the most useful details to understand is the difference between target and currentTarget. The target is the element that originally triggered the event. The currentTarget is the element whose handler is currently running.

Bubbling example
function Toolbar() { const handleClick = (event: React.MouseEvent<HTMLDivElement>) => { console.log('target:', event.target); console.log('currentTarget:', event.currentTarget); }; return ( <div onClick={handleClick}> <button type="button">Run</button> </div> ); }

If the user clicks the button, target is the button, but currentTarget is the wrapping div because that is where the handler is attached. This distinction matters whenever reusable components read dataset values, element measurements, or form references.

Preventing default behavior and stopping propagation

Synthetic events support the same control methods you already know from the DOM, which is why most handler code reads naturally.

Form submit
function LoginForm() { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); console.log('submit blocked so React can handle it'); }; return ( <form onSubmit={handleSubmit}> <input name="email" type="email" /> <button type="submit">Sign in</button> </form> ); }
Stop propagation
function Card() { return ( <div onClick={() => console.log('card clicked')}> <button type="button" onClick={(event) => { event.stopPropagation(); console.log('button clicked only'); }} > Open menu </button> </div> ); }

Async access and the old pooling advice

Older React versions used event pooling. After the handler finished, React could reuse the event object, which meant asynchronous code sometimes saw cleared values unless you called event.persist(). That advice is outdated for modern React. Since React 17, synthetic events are no longer pooled.

Question Modern React answer
Do synthetic events still exist? Yes, React still wraps browser events.
Are they still pooled? No, pooling was removed in React 17.
Do you still need event.persist()? Usually no. In current React it is generally unnecessary.

Even so, there is still a good habit here: if you only need one value, snapshot it early instead of carrying the entire event object through async code.

Snapshot the value
function SearchBox() { const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const nextValue = event.target.value; queueMicrotask(() => { console.log(nextValue); }); }; return <input onChange={handleChange} />; }

When nativeEvent is useful

Most of the time, stay inside the synthetic event interface. But React also exposes the original browser event as nativeEvent for cases where you need lower-level event details.

Access the native event
function PointerExample() { const handleMove = (event: React.MouseEvent<HTMLDivElement>) => { console.log(event.clientX); console.log(event.nativeEvent instanceof MouseEvent); }; return <div onMouseMove={handleMove}>Move here</div>; }

That should be the exception, not the default. If the synthetic event already gives you what you need, relying on nativeEvent just adds coupling to browser-specific details.

TypeScript makes the contract clearer

React's event types are worth using because they make handler intent explicit and help the editor understand what currentTarget actually is.

Typed form handler
type Props = { onSearch: (query: string) => void; }; function SearchForm({ onSearch }: Props) { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const formData = new FormData(event.currentTarget); const query = String(formData.get('query') ?? ''); onSearch(query.trim()); }; return ( <form onSubmit={handleSubmit}> <input name="query" /> <button type="submit">Search</button> </form> ); }

Common mistakes

Old pooling advice In current React, event.persist() is usually unnecessary because pooling was removed.
Wrong property Use currentTarget when you need the element that owns the running handler, not the original clicked child.
Early nativeEvent usage Prefer the normalized synthetic API unless you truly need browser-specific event details.
Assuming raw DOM parity React events feel similar to manual DOM listeners, but they still flow through React's event system.

The short version is simple: synthetic events are React's compatibility layer around browser events. They give you a stable API, they keep normal handlers ergonomic, and they still let you escape to nativeEvent when necessary. That is why they remain useful even though most React code barely mentions them directly.