React 17 removed event pooling, a performance optimization from older versions where event objects were reused. Code that relied on event.persist() to access event properties asynchronously no longer needs it. This change simplifies event handling and eliminates a common source of confusion for developers.
In React 16 and earlier, React used event pooling as a performance optimization. The synthetic event objects were reused between different events to reduce memory allocation in older browsers. After an event handler finished executing, all properties on the event object were nullified and the object was returned to a pool for reuse. This meant developers could not access event properties asynchronously (like in setTimeout or Promise callbacks) without first calling event.persist() to remove the event from the pool. React 17 completely removed this event pooling system because modern JavaScript engines handle object allocation efficiently, making the optimization unnecessary. The removal also eliminated a major source of confusion where developers would encounter null event properties when trying to access them asynchronously.
Search your project for event.persist() or e.persist() calls and remove them. They no longer serve any purpose in React 17+. You can safely access event properties asynchronously without calling persist.
// React 16 and earlier - required event.persist()
function handleClick(event: React.MouseEvent) {
event.persist(); // No longer needed
setTimeout(() => {
console.log(event.type); // Would be null without persist() in React 16
}, 100);
}
// React 17+ - event.persist() not needed
function handleClick(event: React.MouseEvent) {
setTimeout(() => {
console.log(event.type); // Works fine, properties stay accessible
}, 100);
}If you're using eslint-plugin-react, update to the latest version and remove or disable rules related to event pooling. The rule react-hooks/exhaustive-deps and similar may flag event usage in async contexts based on old behavior.
// .eslintrc.json
{
"extends": ["plugin:react/recommended"],
"rules": {
// This rule is no longer relevant for React 17+
"react/no-direct-mutation-state": "off"
}
}While no longer required, you can extract event properties into variables if you prefer explicit code or are working with a mixed React 16/17 codebase. This pattern works in all React versions.
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = event.target.value; // Extract the value explicitly
const name = event.target.name;
setTimeout(() => {
console.log(value, name); // Use extracted values
}, 100);
}Remove any internal documentation, onboarding guides, or code review checklists that mention event.persist() requirements. Update your team's React best practices to reflect React 17+ behavior.
Create a migration note if maintaining a large codebase:
## React 17 Event Pooling Removal
As of React 17:
- event.persist() is no longer needed
- Event properties remain accessible asynchronously
- Existing event.persist() calls are harmless but unnecessary
- Remove them during normal code maintenanceUse your IDE or a tool like ripgrep to find and remove all event.persist() calls across your project:
# Search for event.persist() calls
rg "event\.persist\(\)" --type ts --type tsx --type js --type jsx
# Or use find + sed for automated removal (review changes first!)
find src -name "*.tsx" -o -name "*.ts" | xargs sed -i "/event\.persist()/d"Be sure to test after removing these calls, though they should have no effect.
The event pooling removal is part of React's broader effort to simplify the framework and remove legacy optimizations that modern JavaScript engines make unnecessary. React 17 also changed how events are attached to the DOM, moving from document to the root container. If you maintain a library or framework that wraps React, you may need to update documentation about event handling. React Native still uses event pooling in some cases as of this writing, so if you share code between React and React Native, be aware of platform differences. The event.persist() method is still present on the SyntheticEvent type for backward compatibility, but it's now a no-op function that does nothing. TypeScript types may still include it, which is why the compiler won't flag it as an error even though it's unnecessary. For very large codebases with many contributors, consider adding a custom ESLint rule that flags event.persist() usage to help clean up legacy code over time.
React.FC expects children prop to be defined
React.FC no longer includes implicit children prop
Warning: You provided a `selected` prop to a form field without an `onChange` handler
You provided a 'selected' prop without an onChange handler
Failed to load source map from suspense chunk
How to fix "Failed to load source map from suspense chunk" in React
Prop spreading could cause security issues
Prop spreading could cause security issues
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency