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.
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.
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.
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>
);
}
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.
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.
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.
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
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.