diff --git a/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js b/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js index 4a6c0a841276d..60525c8c842c7 100644 --- a/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js +++ b/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js @@ -156,13 +156,45 @@ assign(SyntheticEvent.prototype, { * `PooledClass` looks for `destructor` on each instance it releases. */ destructor: function() { + var setNull = setToNullOrWarning.bind(this) var Interface = this.constructor.Interface; for (var propName in Interface) { - this[propName] = null; + setNull(propName); + } + var otherProps = ['dispatchConfig', '_targetInst', 'nativeEvent']; + otherProps.forEach(setNull); + + function setToNullOrWarning(propName) { + if (__DEV__) { + Object.defineProperty(this, propName, { + set: function(val) { + // no-op + var warningCondition = false; + warning( + warningCondition, + 'This synthetic event is reused for performance reasons. If you\'re ' + + 'seeing this, you\'re setting property `' + propName + '` on a ' + + 'released/nullified synthetic event. This is effectively a no-op. See ' + + 'https://fb.me/react-event-pooling for more information.' + ); + return val; + }, + get: function() { + var warningCondition = false; + warning( + warningCondition, + 'This synthetic event is reused for performance reasons. If you\'re ' + + 'seeing this, you\'re accessing property `' + propName + '` on a ' + + 'released/nullified synthetic event. This is set to null. See ' + + 'https://fb.me/react-event-pooling for more information.' + ); + return null; + } + }) + } else { + this[propName] = null; + } } - this.dispatchConfig = null; - this._targetInst = null; - this.nativeEvent = null; }, }); diff --git a/src/renderers/dom/client/syntheticEvents/__tests__/SyntheticEvent-test.js b/src/renderers/dom/client/syntheticEvents/__tests__/SyntheticEvent-test.js index e7e3d29fe36ef..851b6c2259d6c 100644 --- a/src/renderers/dom/client/syntheticEvents/__tests__/SyntheticEvent-test.js +++ b/src/renderers/dom/client/syntheticEvents/__tests__/SyntheticEvent-test.js @@ -73,6 +73,7 @@ describe('SyntheticEvent', function() { }); it('should be nullified if the synthetic event has called destructor', function() { + spyOn(console, 'error'); var target = document.createElement('div'); var syntheticEvent = createEvent({srcElement: target}); syntheticEvent.destructor(); @@ -81,13 +82,43 @@ describe('SyntheticEvent', function() { expect(syntheticEvent.target).toBe(null); }); + it('should warn when accessing properties of a destructored synthetic event', function() { + spyOn(console, 'error'); + var target = document.createElement('div'); + var syntheticEvent = createEvent({srcElement: target}); + syntheticEvent.destructor(); + expect(syntheticEvent.type).toBe(null); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toBe( + 'Warning: This synthetic event is reused for performance reasons. If ' + + 'you\'re seeing this, you\'re accessing a property on a ' + + 'released/nullified synthetic event. This is set to null. See ' + + 'https://fb.me/react-event-pooling for more information.' + ); + }); + + it('should warn when setting properties of a destructored synthetic event', function() { + spyOn(console, 'error'); + var target = document.createElement('div'); + var syntheticEvent = createEvent({srcElement: target}); + syntheticEvent.destructor(); + expect(syntheticEvent.type = 'MouseEvent').toBe('MouseEvent'); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toBe( + 'Warning: This synthetic event is reused for performance reasons. If ' + + 'you\'re seeing this, you\'re setting a property on a ' + + 'released/nullified synthetic event. This is effectively a no-op. See ' + + 'https://fb.me/react-event-pooling for more information.' + ); + }); + it('should warn if the synthetic event has been released when calling `preventDefault`', function() { spyOn(console, 'error'); var syntheticEvent = createEvent({}); SyntheticEvent.release(syntheticEvent); syntheticEvent.preventDefault(); - expect(console.error.calls.length).toBe(1); - expect(console.error.argsForCall[0][0]).toBe( + expect(console.error.calls.length).toBe(3); // once each for setting `defaultPrevented`, accessing `nativeEvent`, and accessing `preventDefault` + expect(console.error.argsForCall[2][0]).toBe( 'Warning: This synthetic event is reused for performance reasons. If ' + 'you\'re seeing this, you\'re calling `preventDefault` on a ' + 'released/nullified synthetic event. This is a no-op. See ' + @@ -95,13 +126,13 @@ describe('SyntheticEvent', function() { ); }); - it('should warn if the synthetic event has been released when calling `stopPropagation`', function() { + iit('should warn if the synthetic event has been released when calling `stopPropagation`', function() { spyOn(console, 'error'); var syntheticEvent = createEvent({}); SyntheticEvent.release(syntheticEvent); syntheticEvent.stopPropagation(); - expect(console.error.calls.length).toBe(1); - expect(console.error.argsForCall[0][0]).toBe( + expect(console.error.calls.length).toBe(2); // once each for accessing `nativeEvent` and accessing `setPropogation` + expect(console.error.argsForCall[1][0]).toBe( 'Warning: This synthetic event is reused for performance reasons. If ' + 'you\'re seeing this, you\'re calling `stopPropagation` on a ' + 'released/nullified synthetic event. This is a no-op. See ' +