How to Update Event Pooling in React
This article explains how event pooling works in React, how its behavior changed in modern versions, and how you should update your codebase to handle these changes. You will learn how React 17 completely removed event pooling, making asynchronous event access straightforward, and what steps you need to take if you are migrating from React 16 or earlier.
Understanding Event Pooling in React 16 and Earlier
In React 16 and older versions, React used a system called event
pooling to improve performance. Instead of creating a new event object
for every single user interaction, React reused a single, global
SyntheticEvent instance across multiple events.
Once an event handler completed its execution, React would clear all
properties on the SyntheticEvent object, setting them back
to null so the object could be reused. Because of this
pooling system, you could not access event properties (like
e.target.value) inside asynchronous operations such as
setTimeout, promises, or state updates.
To prevent React from nullifying the event properties in React 16,
you had to call e.persist() manually:
// React 16 approach
function handleChange(e) {
// Retain the event properties for asynchronous use
e.persist();
setTimeout(() => {
console.log(e.target.value); // Works because of e.persist()
}, 100);
}The React 17+ Update: Event Pooling Removed
Starting with React 17, event pooling was completely removed from the library. Modern browsers are fast enough that reusing event objects no longer provides a noticeable performance benefit, while the pooling mechanism frequently confused developers.
In React 17, 18, and 19, SyntheticEvent objects are no
longer reused. They behave like standard DOM events, meaning their
properties remain accessible even within asynchronous code.
How to Update Your React Code
Because of this architectural change, updating your code depends entirely on the version of React you are currently using.
1. If you are upgrading to React 17 or newer:
You do not need to make any complex changes. Because event pooling no
longer exists, you can safely remove all calls to
e.persist() throughout your application. While keeping
e.persist() in your code will not cause errors—it has been
turned into a “no-op” (a function that does nothing)—removing it cleans
up your codebase.
// React 17+ updated approach (No e.persist() needed)
function handleChange(e) {
setTimeout(() => {
console.log(e.target.value); // Works out-of-the-box
}, 100);
}2. If you must remain on React 16:
If you cannot upgrade your React version yet, you must continue to
use e.persist() whenever you need to access event
properties inside asynchronous callbacks. Alternatively, you can extract
the specific values you need from the event object synchronously before
entering the asynchronous context:
// Alternative React 16 approach (Caching values synchronously)
function handleChange(e) {
const value = e.target.value; // Store the value immediately
setTimeout(() => {
console.log(value); // Safely accesses the cached string
}, 100);
}