SyntheticEvent Pooling Warning in React 16.x when using applyReactInVue
When using React components inside a Vue environment via applyReactInVue,
a warning occurs in React 16.x environments if a SyntheticEvent is passed to Vue
and later accessed again in an asynchronous execution context.
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the method `timeStamp` on a released/nullified synthetic event.
This issue is not caused by a specific component or an external library.
Instead, it occurs because
veaury’s current event-passing behavior
exposes React SyntheticEvent objects outside the React layer into Vue
which structurally allows them to escape the React event lifecycle.
This document explains:
- Why this warning is reproducible only in React 16.x
- Under which scenarios the problem occurs
- The workaround we applied inside veaury
Environment
This warning occurs under the following conditions:
- Vue 3
- veaury
applyReactInVue
- React 16.x (SyntheticEvent pooling is aggressively enabled)
- A SyntheticEvent is
- passed to the Vue layer, and
- later accessed again in an async / delayed execution flow
⚠ In React 17 / 18 the same code rarely produces this warning
due to differences in pooling behavior and release timing.
Therefore, this issue is relevant specifically to React 16.x users.
Problem Description
Currently, applyReactInVue creates React event handlers like:
<Component {...this.$attrs} // onClick / onInput / onChange ... {...otherProps} />
meaning Vue $attrs are passed directly to the React component.
In this setup:
- React creates a
SyntheticEvent instance
- That object is passed to the Vue event handler
- Vue code later accesses the event again inside an async callback
Under React 16.x pooling rules:
the SyntheticEvent has already been released back to the pool,
but the Vue layer continues to reference it
This triggers the warning:
SyntheticEvent is reused / nullified
In other words:
a SyntheticEvent whose lifecycle has already ended inside React
becomes accessible outside the framework boundary (Vue layer)
which is the root cause of this issue.
Reproduction Example
Vue
<!-- src/views/TestReactPooling.vue -->
<template>
<div style="padding: 20px">
<h1>React SyntheticEvent Pooling Test (Vue3 + veaury)</h1>
<p>Click the button and the Vue handler will read e.timeStamp asynchronously.</p>
<TestButton @click="handleClick" />
</div>
</template>
<script setup>
import { applyReactInVue } from '../../lib/vueinReact'
import TestButtonReact from '../../react_app/TestButton.jsx'
// Wrap with veaury
const TestButton = applyReactInVue(TestButtonReact)
// Vue receives a SyntheticEvent here
const handleClick = (e) => {
// Synchronous access: still valid
console.log('[sync] e.timeStamp:', e.timeStamp)
// Asynchronous access
setTimeout(() => {
// React 16.x triggers pooling warning here:
// "Warning: This synthetic event is reused for performance reasons..."
console.log('[async] e.timeStamp:', e.timeStamp)
}, 0)
}
</script>
React
// src/react_app/TestButton.jsx
import React from 'react';
class TestButton extends React.Component {
// Compatible with React 16.4 — class fields not required
handleClick(e) {
// Pass SyntheticEvent directly to Vue
if (this.props.onClick) {
this.props.onClick(e);
}
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
Click (pooling test)
</button>
);
}
}
export default TestButton;
Root Cause Summary
In React 16.x:
- SyntheticEvent objects are returned to the pool after the event handler completes
- Accessing them afterward triggers warnings
However, the current veaury event-passing behavior:
- forwards the SyntheticEvent object
- directly to the Vue layer and userland code
Developers on the Vue side have no indication that the object is a SyntheticEvent
and do not have an opportunity to call event.persist().
Therefore:
passing SyntheticEvent objects across framework boundaries
is incompatible with the React 16.x event lifecycle.
Applied Fix (Workaround)
In our project, we resolved this by:
- Calling
event.persist() before leaving the React handler
to guarantee the event is not pooled
- Passing only
nativeEvent (with fallback) to the Vue layer
instead of exposing the SyntheticEvent object itself
__veauryWrapReactEventListener__(fn) {
if (typeof fn !== 'function') return fn;
return (e, ...rest) => {
// Prevent SyntheticEvent pooling in React 16.x
if (e && typeof e.persist === 'function') {
e.persist();
}
// Only pass nativeEvent to the Vue layer
const native = e?.nativeEvent ?? e;
return fn(native, ...rest);
};
}
We also automatically applied this wrapper
to event handlers passed via $attrs.
const attrsWithWrappedEvents = {};
Object.keys(this.$attrs || {}).forEach((key) => {
const val = this.$attrs[key];
if (key.startsWith('on') && typeof val === 'function') {
attrsWithWrappedEvents[key] =
this.__veauryWrapReactEventListener__(val);
} else {
attrsWithWrappedEvents[key] = val;
}
});
Result
- React 16.x
- SyntheticEvent pooling warnings no longer occur
- React 17+
- Behavior remains identical, no side effects observed
Scope of Impact
- Affects React 16.x only
- Behavior is backward-safe
- No behavior change in React 17+ environments
- No API changes for veaury users
Proposal
In the current architecture:
- React event objects may propagate
- beyond React and into Vue or external user code
which leads to pooling warnings in React 16.x environments.
We propose the following improvement:
In short:
SyntheticEvent objects should not be exposed directly
across framework boundaries by default.
This approach would allow veaury to maintain compatibility
with the React 16.x event lifecycle while remaining safe for newer versions.
SyntheticEvent Pooling Warning in React 16.x when using
applyReactInVueWhen using React components inside a Vue environment via
applyReactInVue,a warning occurs in React 16.x environments if a SyntheticEvent is passed to Vue
and later accessed again in an asynchronous execution context.
This issue is not caused by a specific component or an external library.
Instead, it occurs because
which structurally allows them to escape the React event lifecycle.
This document explains:
Environment
This warning occurs under the following conditions:
applyReactInVueTherefore, this issue is relevant specifically to React 16.x users.
Problem Description
Currently,
applyReactInVuecreates React event handlers like:<Component {...this.$attrs} // onClick / onInput / onChange ... {...otherProps} />meaning Vue
$attrsare passed directly to the React component.In this setup:
SyntheticEventinstanceUnder React 16.x pooling rules:
This triggers the warning:
SyntheticEvent is reused / nullifiedIn other words:
which is the root cause of this issue.
Reproduction Example
Vue
React
Root Cause Summary
In React 16.x:
However, the current veaury event-passing behavior:
Developers on the Vue side have no indication that the object is a SyntheticEvent
and do not have an opportunity to call
event.persist().Therefore:
Applied Fix (Workaround)
In our project, we resolved this by:
event.persist()before leaving the React handlerto guarantee the event is not pooled
nativeEvent(with fallback) to the Vue layerinstead of exposing the SyntheticEvent object itself
We also automatically applied this wrapper
to event handlers passed via
$attrs.Result
Scope of Impact
Proposal
In the current architecture:
which leads to pooling warnings in React 16.x environments.
We propose the following improvement:
when forwarding events inside veaury
event.persist()nativeEventor a cloned event object to VueIn short:
This approach would allow veaury to maintain compatibility
with the React 16.x event lifecycle while remaining safe for newer versions.