What are Synthetic Events in React?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Medium
#React

Brief Answer

Synthetic Events are a cross-browser wrapper over native browser events provided by React. They ensure a consistent API for working with events across all browsers and add some useful features.

Synthetic Events advantages:

  • Cross-browser compatibility
  • Automatic event pooling (improves performance)
  • Unified API for all browsers
  • Compatibility with React Fiber

Features:

  • Not all native event properties are directly accessible
  • Need to use event.persist() for asynchronous access
  • Differs from native events in some aspects

Key rule: Use Synthetic Events by default, switch to native events only when necessary! 🎯


Full Answer

Imagine you’re driving in different countries. Each country has different traffic rules and different steering wheels. Synthetic Events are like an international driver’s license that allows you to drive the same way everywhere! 🚗

What are Synthetic Events

Synthetic Events are React’s event system that provides a unified interface for working with events:

// Synthetic Event in action
function ButtonComponent() {
  const handleClick = (event) => {
    // event is a SyntheticEvent, not a native event!
    console.log(event.target); // Works in all browsers
    console.log(event.type);   // Always correctly identifies event type
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

Why Synthetic Events are needed

Synthetic Events solve cross-browser compatibility issues:

// Without Synthetic Events - problems:
// In&nbsp;old IE: event.target vs event.srcElement
// In&nbsp;different browsers: different method names
// Different event objects
 
// With Synthetic Events - it's simple:
function CrossBrowserComponent() {
  const handleChange = (event) => {
    // Always event.target, regardless of&nbsp;browser
    console.log(event.target.value);
  };
  
  return <input onChange={handleChange} />;
}

How Synthetic Events work

1. Unified event system

React uses event delegation at the root level:

// React automatically delegates events
function EventDelegation() {
  const handleListClick = (event) => {
    // One handler for&nbsp;all list items
    if (event.target.tagName === 'BUTTON') {
      console.log('Button clicked:', event.target.dataset.id);
    }
  };
  
  return (
    <ul onClick={handleListClick}>
      <li><button data-id="1">Item 1</button></li>
      <li><button data-id="2">Item 2</button></li>
      <li><button data-id="3">Item 3</button></li>
    </ul>
  );
}

2. Event pooling for performance

React reuses event objects:

// Problem with event pooling
function ProblematicComponent() {
  const handleClick = (event) => {
    // ❌ event will be&nbsp;null after synchronous code
    setTimeout(() => {
      console.log(event.target); // undefined!
    }, 0);
  };
  
  return <button onClick={handleClick}>Problematic button</button>;
}
 
// ✅ Solution with persist()
function GoodComponent() {
  const handleClick = (event) => {
    // Preserve event for&nbsp;asynchronous use
    event.persist();
    
    setTimeout(() => {
      console.log(event.target); // Works!
    }, 0);
  };
  
  return <button onClick={handleClick}>Good button</button>;
}

Difference from native events

1. Properties and methods

function EventComparison() {
  const syntheticHandler = (syntheticEvent) => {
    // Synthetic Event
    console.log(syntheticEvent.target);
    console.log(syntheticEvent.type);
    // syntheticEvent.stopPropagation() - works
    // syntheticEvent.preventDefault() - works
  };
  
  const nativeHandler = () => {
    // Native event
    const nativeEvent = document.getElementById('myButton')
      .addEventListener('click', (event) => {
        console.log(event.target);
        console.log(event.type);
        // event.stopPropagation() - works
        // event.preventDefault() - works
      });
  };
  
  return <button onClick={syntheticHandler} id="myButton">Comparison</button>;
}

2. Access to native event

function AccessingNativeEvent() {
  const handleClick = (syntheticEvent) => {
    // Access native event
    const nativeEvent = syntheticEvent.nativeEvent;
    
    console.log('Synthetic:', syntheticEvent);
    console.log('Native:', nativeEvent);
    
    // Both events work, but have different properties
  };
  
  return <button onClick={handleClick}>Access native event</button>;
}

When to use native events

1. Third-party libraries

function ThirdPartyIntegration() {
  const divRef = useRef(null);
  
  useEffect(() => {
    // Some libraries require native events
    const jqueryElement = $(divRef.current);
    jqueryElement.on('customEvent', (nativeEvent) => {
      // Native event needed here
      console.log(nativeEvent);
    });
    
    return () => {
      jqueryElement.off('customEvent');
    };
  }, []);
  
  return <div ref={divRef}>jQuery integration</div>;
}

2. Specific event properties

function SpecificEventProperties() {
  const handleTouch = (syntheticEvent) => {
    // Some specific properties are only available in native event
    const nativeEvent = syntheticEvent.nativeEvent;
    
    if (nativeEvent.touches) {
      console.log('Touch count:', nativeEvent.touches.length);
      console.log('First touch:', nativeEvent.touches[0]);
    }
  };
  
  return <div onTouchMove={handleTouch}>Touch surface</div>;
}

Common mistakes

1. Ignoring event pooling

// ❌ Common mistake
function BadAsyncAccess() {
  const handleClick = (event) => {
    // event will be&nbsp;null in&nbsp;asynchronous code
    fetch('/api/data').then(() => {
      console.log(event.target); // ❌ undefined!
    });
  };
  
  return <button onClick={handleClick}>Error</button>;
}
 
// ✅ Correct solution
function GoodAsyncAccess() {
  const handleClick = (event) => {
    // Save reference to&nbsp;target
    const target = event.target;
    
    fetch('/api/data').then(() => {
      console.log(target); // ✅ Works!
    });
  };
  
  return <button onClick={handleClick}>Correct</button>;
}

2. Mixing approaches

// ❌ Bad practice
function BadMixing() {
  const handleClick = (syntheticEvent) => {
    // Don't mix synthetic and native events unnecessarily
    syntheticEvent.nativeEvent.stopPropagation();
    syntheticEvent.preventDefault();
  };
  
  return <button onClick={handleClick}>Bad mix</button>;
}
 
// ✅ Good practice
function GoodPractice() {
  const handleClick = (syntheticEvent) => {
    // Use Synthetic Event API
    syntheticEvent.stopPropagation();
    syntheticEvent.preventDefault();
  };
  
  return <button onClick={handleClick}>Good practice</button>;
}

Summary

Synthetic Events are like a universal translator between your code and browsers! 🌍

  • Synthetic Events - cross-browser wrapper from React
  • Native events - original browser events

When to use what:

  • By default: Synthetic Events
  • Third-party libraries: Native events
  • Specific properties: Native events

Practical tips:

  1. Use Synthetic Events in 95% of cases
  2. Remember about event pooling when working asynchronously
  3. Use event.persist() only when necessary
  4. Switch to native events only when absolutely necessary

Synthetic Events are one of the reasons why React is so popular: it takes the complexity of cross-browser compatibility off your shoulders, allowing you to focus on application logic! 💪


Want more useful React articles? Subscribe to EasyAdvice, bookmark the site and level up every day! 🚀