How to Update Synthetic Events in React

This article explains how to handle and update Synthetic Events in React. You will learn how React manages these events, the crucial differences between React 16 and React 17+ regarding event pooling, and the best practices for capturing and updating event data during asynchronous operations.

Understanding React Synthetic Events

A Synthetic Event is a cross-browser wrapper around the browser’s native event. React wraps native events to ensure that your code behaves consistently across different browsers. Synthetic events have the same interface as native events, including methods like preventDefault() and stopPropagation().

The Shift in React 17: No More Event Pooling

In React 16 and earlier, React used event pooling. This meant that Synthetic Event objects were reused for performance reasons. Once an event callback was executed, all properties on the event object were nullified, making it impossible to access the event asynchronously without calling event.persist().

Starting with React 17, event pooling was removed. Synthetic Event properties no longer disappear after the callback is executed. You can now access event properties directly inside asynchronous code (such as setTimeout or promise chains) without needing to update or persist the event manually.

How to Update State Using Synthetic Events

Because Synthetic Events represent read-only user actions, you do not update the event object itself. Instead, you use the data from the event to update your component’s state.

Here is the standard pattern for updating state using a Synthetic Event:

import React, { useState } from 'react';

function TextInput() {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    // Access the synthetic event's target value and update state
    setValue(event.target.value);
  };

  return <input type="text" value={value} onChange={handleChange} />;
}

Best Practices for Asynchronous Updates

If you need to use event data inside an asynchronous function, the safest and most efficient practice is to extract the values you need immediately, rather than passing the entire event object into the asynchronous scope.

Extracting the properties directly from the event object ensures your code remains clean, easy to test, and highly performant.

const handleSearch = (event) => {
  // Extract the value immediately
  const query = event.target.value;

  // Use the extracted value in an asynchronous operation
  setTimeout(() => {
    console.log(`Searching for: ${query}`);
  }, 1000);
};

Legacy Approach: Event Persistence (React 16 and older)

If you are maintaining an older React application (React 16 or below) and must pass the entire event to an asynchronous function, you must call event.persist(). This removes the event from the pool and prevents its properties from being nullified.

const handleLegacyChange = (event) => {
  // Prevents React from clearing the event properties
  event.persist();

  setTimeout(() => {
    // Safe to access in React 16 because of event.persist()
    console.log(event.target.value); 
  }, 1000);
};