frontend / react / synthetic_events.md

Synthetic Events in React

4 interview angles 6 min read source

Synthetic Events in React

Synthetic events are React’s cross-browser wrapper around the browser’s native events. They provide a consistent API regardless of the browser being used.

Table of Contents


What are Synthetic Events?

Synthetic events are React’s wrapper around native DOM events. They provide a consistent interface across different browsers and normalize event behavior.

Key Characteristics:

  • Cross-browser: Work consistently across all browsers
  • Normalized: Same API regardless of browser differences
  • Pooled: Events are reused for performance
  • Synthetic: Not actual DOM events, but React’s abstraction

Event Object Structure:

// Synthetic event object
const syntheticEvent = {
  type: 'click',
  target: DOMElement,
  currentTarget: DOMElement,
  preventDefault: function() { /* ... */ },
  stopPropagation: function() { /* ... */ },
  nativeEvent: DOMEvent, // Access to original DOM event
  // ... other normalized properties
};

Event Handling

Basic Event Handling:

function Button() {
  const handleClick = (event) => {
    console.log('Button clicked!');
    console.log('Event type:', event.type);
    console.log('Target:', event.target);
    console.log('Native event:', event.nativeEvent);
  };

  return (
    <button onClick={handleClick}>
      Click Me
    </button>
  );
}

Event Prevention:

function Form() {
  const handleSubmit = (event) => {
    event.preventDefault(); // Prevent form submission
    event.stopPropagation(); // Stop event bubbling

    console.log('Form submitted');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" />
      <button type="submit">Submit</button>
    </form>
  );
}

Event Parameters:

function InputField() {
  const handleChange = (event) => {
    const value = event.target.value;
    console.log('Input value:', value);
  };

  const handleKeyPress = (event) => {
    if (event.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  return (
    <input
      onChange={handleChange}
      onKeyPress={handleKeyPress}
      placeholder="Type something..."
    />
  );
}

Multiple Event Handlers:

function InteractiveElement() {
  const handleMouseEnter = (event) => {
    console.log('Mouse entered');
  };

  const handleMouseLeave = (event) => {
    console.log('Mouse left');
  };

  const handleClick = (event) => {
    console.log('Clicked');
  };

  return (
    <div
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      onClick={handleClick}
      style={{ padding: '20px', border: '1px solid black' }}
    >
      Hover and click me
    </div>
  );
}

Event Pooling

What is Event Pooling:

React reuses event objects for performance. After an event handler finishes, the event object is returned to the pool and its properties are nullified.

Event Pooling Example:

function EventPoolingExample() {
  const handleClick = (event) => {
    // Good: Access event properties immediately
    console.log('Event type:', event.type);
    console.log('Target:', event.target);

    // Bad: Accessing event properties asynchronously
    setTimeout(() => {
      console.log('Event type:', event.type); // null
      console.log('Target:', event.target); // null
    }, 100);
  };

  return (
    <button onClick={handleClick}>
      Click Me
    </button>
  );
}

Handling Event Pooling:

function EventPoolingSolution() {
  const handleClick = (event) => {
    // Good: Store values immediately
    const eventType = event.type;
    const target = event.target;

    setTimeout(() => {
      console.log('Event type:', eventType); // Works
      console.log('Target:', target); // Works
    }, 100);
  };

  return (
    <button onClick={handleClick}>
      Click Me
    </button>
  );
}

Using event.persist():

function PersistExample() {
  const handleClick = (event) => {
    // Good: Persist the event for async use
    event.persist();

    setTimeout(() => {
      console.log('Event type:', event.type); // Works
      console.log('Target:', event.target); // Works
    }, 100);
  };

  return (
    <button onClick={handleClick}>
      Click Me
    </button>
  );
}

Best Practices

1. Access Event Properties Immediately:

// Good: Access properties immediately
function GoodHandler(event) {
  const value = event.target.value;
  const type = event.type;

  // Use values immediately or store them
  console.log(value, type);
}

// Bad: Access properties asynchronously
function BadHandler(event) {
  setTimeout(() => {
    console.log(event.target.value); // null
  }, 100);
}

2. Use event.persist() for Async Operations:

// Good: Persist event for async use
function AsyncHandler(event) {
  event.persist();

  fetch('/api/data')
    .then(() => {
      console.log('Event type:', event.type); // Works
    });
}

// Good: Store values immediately
function BetterAsyncHandler(event) {
  const eventData = {
    type: event.type,
    target: event.target,
    value: event.target.value
  };

  fetch('/api/data', {
    method: 'POST',
    body: JSON.stringify(eventData)
  });
}

3. Prevent Default Behavior:

// Good: Prevent default when needed
function FormHandler(event) {
  event.preventDefault();

  // Custom form handling
  const formData = new FormData(event.target);
  console.log('Form data:', formData);
}

// Good: Stop propagation when needed
function NestedHandler(event) {
  event.stopPropagation();

  // Handle event without bubbling to parent
  console.log('Handled locally');
}

4. Use Proper Event Types:

// Good: Use appropriate event handlers
function InputComponent() {
  const handleChange = (event) => {
    console.log('Value changed:', event.target.value);
  };

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  const handleFocus = (event) => {
    console.log('Input focused');
  };

  return (
    <input
      onChange={handleChange}
      onKeyDown={handleKeyDown}
      onFocus={handleFocus}
    />
  );
}

5. Handle Multiple Events Efficiently:

// Good: Single handler for multiple events
function EfficientHandler() {
  const handleInteraction = (event) => {
    switch (event.type) {
      case 'click':
        console.log('Clicked');
        break;
      case 'mouseenter':
        console.log('Mouse entered');
        break;
      case 'mouseleave':
        console.log('Mouse left');
        break;
      default:
        break;
    }
  };

  return (
    <div
      onClick={handleInteraction}
      onMouseEnter={handleInteraction}
      onMouseLeave={handleInteraction}
    >
      Interactive element
    </div>
  );
}

Common Interview Questions

Q: What are synthetic events in React?

  • React’s cross-browser wrapper around native DOM events that provides a consistent API.

Q: Why does React use synthetic events?

  • To ensure consistent behavior across different browsers and normalize event handling.

Q: What is event pooling in React?

  • React reuses event objects for performance, nullifying their properties after the event handler finishes.

Q: How do you handle events in async operations?

  • Use event.persist() or store event properties immediately before async operations.

Q: What’s the difference between event.target and event.currentTarget?

  • event.target is the element that triggered the event, while event.currentTarget is the element that the event handler is attached to.

Q: How do you prevent default behavior in React?

  • Use event.preventDefault() to prevent the default browser behavior.

Q: How do you stop event propagation?

  • Use event.stopPropagation() to prevent the event from bubbling up to parent elements.

Q: Can you access the native DOM event?

  • Yes, through event.nativeEvent property.

Summary

  • Synthetic events are React’s cross-browser wrapper around native DOM events
  • Event pooling reuses event objects for performance optimization
  • Access event properties immediately or use event.persist() for async operations
  • preventDefault() and stopPropagation() control event behavior
  • event.target vs event.currentTarget have different meanings
  • event.nativeEvent provides access to the original DOM event
  • Synthetic events ensure consistent behavior across all browsers
  • Proper event handling is crucial for React performance and functionality

Interview angle

  • “What is a synthetic event?” - React’s cross-browser wrapper over the native event, with a consistent API. Since React 17 listeners are attached at the root container rather than document, which is what makes multiple React roots and React-inside-non-React apps behave.
  • “Do you still need e.persist()?” - no. Event pooling was removed in React 17, so the event object survives into async callbacks. Advice telling you to call persist() predates that.
  • “How does bubbling work through portals?” - along the React tree, not the DOM tree. A click inside a portalled modal reaches handlers on its React ancestors even though the DOM node is elsewhere.
  • “When do you reach for a native listener?” - for events React does not synthesise or that need non-passive preventDefault on scroll and touch, and for listeners on window or document. Add them in an effect and remove them in the cleanup.