Skip to main content
Practice Problems

Synthetic events in React

What are Synthetic Events?

Synthetic Events are React's wrapper around native browser events. React creates a unified event system that works the same across all browsers.

javascript
function Button() { function handleClick(event) { console.log(event); // SyntheticEvent, not native Event } return <button onClick={handleClick}>Click me</button>; }

Why are they needed?

Cross-browser compatibility

Different browsers have different event implementations. Synthetic Events unify the API:

javascript
function Input() { function handleChange(event) { // event.target.value works the same everywhere console.log(event.target.value); } return <input onChange={handleChange} />; }

Performance

React uses event delegation: all handlers are attached to the root element, not to each element separately.

javascript
// Instead of 1000 handlers on each button // React creates one handler at the root function List({ items }) { return ( <ul> {items.map(item => ( <li key={item.id}> <button onClick={() => console.log(item.id)}> {item.name} </button> </li> ))} </ul> ); }

Synthetic Event API

Synthetic Event has the same interface as native events:

javascript
function Form() { function handleSubmit(event) { event.preventDefault(); // Works like native event event.stopPropagation(); console.log(event.type); // 'submit' console.log(event.target); // element reference console.log(event.currentTarget); // element with handler } return ( <form onSubmit={handleSubmit}> <button type="submit">Submit</button> </form> ); }

Event Pooling (before React 17)

In React 16 and earlier, events were reused for performance:

javascript
// React 16 and earlier function handleClick(event) { console.log(event.type); // 'click' setTimeout(() => { console.log(event.type); // null, event cleared! }, 0); }

To preserve an event, you used event.persist():

javascript
function handleClick(event) { event.persist(); // Preserve event setTimeout(() => { console.log(event.type); // 'click', works! }, 0); }

React 17+:

In React 17, event pooling was removed. Events are no longer cleared, event.persist() is not needed.


Accessing native event

You can get the native event through nativeEvent:

javascript
function handleClick(event) { console.log(event); // SyntheticEvent console.log(event.nativeEvent); // Native browser Event // Useful for browser-specific features console.log(event.nativeEvent.path); }

Differences from native events

Naming

React uses camelCase instead of lowercase:

javascript
// HTML <button onclick="handleClick()">Click</button> // React <button onClick={handleClick}>Click</button>

Returning false

In HTML you can return false to prevent default action:

html
<!-- HTML --> <a href="#" onclick="console.log('clicked'); return false"> Link </a>

In React you must explicitly call preventDefault():

javascript
// React function Link() { function handleClick(event) { event.preventDefault(); console.log('clicked'); } return <a href="#" onClick={handleClick}>Link</a>; }

Supported events

React supports all standard DOM events:

Mouse events

javascript
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp

Keyboard events

javascript
function Input() { function handleKeyDown(event) { if (event.key === 'Enter') { console.log('Enter pressed'); } } return <input onKeyDown={handleKeyDown} />; }

Form events

javascript
function Form() { return ( <form onSubmit={e => e.preventDefault()} onChange={e => console.log('changed')} onFocus={e => console.log('focused')} onBlur={e => console.log('blurred')} > <input type="text" /> </form> ); }

Focus events

javascript
onFocus onBlur

Touch events

javascript
onTouchStart onTouchMove onTouchEnd onTouchCancel

Event delegation

React 16 and earlier

Events were attached to document:

javascript
// React attached handlers to document document.addEventListener('click', handleAllClicks);

React 17+

Events are attached to the React root node:

javascript
// React attaches to app root const root = document.getElementById('root'); root.addEventListener('click', handleAllClicks);

This is important when using multiple React versions on one page or integrating with other libraries.


Event handling specifics

onChange vs onInput

In React onChange works like native onInput — fires on every change:

javascript
function Input() { const [value, setValue] = useState(''); return ( <input value={value} // Fires on every keystroke onChange={e => setValue(e.target.value)} /> ); }

onScroll

onScroll doesn't bubble in React (as in DOM), but React provides it for convenience:

javascript
function ScrollableDiv() { function handleScroll(event) { console.log(event.target.scrollTop); } return ( <div onScroll={handleScroll} style={{ height: 200, overflow: 'auto' }}> {/* Lots of content */} </div> ); }

Direct native handler attachment

Sometimes you need to attach a native handler directly:

javascript
function Component() { const ref = useRef(null); useEffect(() => { const element = ref.current; function handleNativeClick(event) { console.log('Native click', event); } element.addEventListener('click', handleNativeClick); return () => { element.removeEventListener('click', handleNativeClick); }; }, []); return <div ref={ref}>Click me</div>; }

Warning:

Be careful with direct handlers — don't forget to remove them in cleanup function.


Event capturing

React supports the capture phase:

javascript
function Parent() { return ( <div onClickCapture={() => console.log('1. Parent capture')} onClick={() => console.log('3. Parent bubble')} > <button onClickCapture={() => console.log('2. Button capture')} onClick={() => console.log('4. Button bubble')} > Click </button> </div> ); } // Clicking button outputs: // 1. Parent capture // 2. Button capture // 4. Button bubble // 3. Parent bubble

TypeScript typing

typescript
import { MouseEvent, ChangeEvent, FormEvent } from 'react'; function Component() { function handleClick(event: MouseEvent<HTMLButtonElement>) { console.log(event.currentTarget.name); } function handleChange(event: ChangeEvent<HTMLInputElement>) { console.log(event.target.value); } function handleSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); } return ( <form onSubmit={handleSubmit}> <input onChange={handleChange} /> <button onClick={handleClick}>Submit</button> </form> ); }

Common mistakes

Asynchronous event usage (React 16)

javascript
// Wrong in React 16 function handleClick(event) { setTimeout(() => { console.log(event.type); // null! }, 0); } // Correct function handleClick(event) { const eventType = event.type; // Save value setTimeout(() => { console.log(eventType); // 'click' }, 0); }

Passing event to setState

javascript
// Wrong function handleChange(event) { setState(prevState => ({ ...prevState, value: event.target.value // event might be cleared })); } // Correct function handleChange(event) { const newValue = event.target.value; setState(prevState => ({ ...prevState, value: newValue })); }

Conclusion

Synthetic Events:

  • Wrapper around native browser events
  • Provide cross-browser compatibility
  • Use delegation for performance
  • Have the same API as native events
  • In React 17+ are not cleared automatically
  • Use camelCase for naming
  • Native event accessible through nativeEvent

In interviews:

Important to be able to:

  • Explain what Synthetic Events are and why they're needed
  • Describe event delegation
  • Explain differences from native events
  • Describe changes in React 17 (event pooling)
  • Show how to access native event

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems