Add warning when reading from event which has been returned to the pool #5940

Merged
merged 1 commit into from Feb 18, 2016

Projects

None yet

5 participants

@kentcdodds
Contributor

This is a WIP. I just want to make sure that I'm headed in the right direction for solving #5939

I'm not certain where the logic for deconstructing SyntheticEvents occurs. My guess is it's an abstraction that utilizes the EventInterface.

Also, what's the proper way to reference NODE_ENV for doing this only in development mode.

Thank you for helping a newbie to the codebase :-)

@gaearon
Member
gaearon commented Jan 29, 2016

Rather than throw, I think it should generate a warning.
Here is a another work-in-progress PR you can use as a reference: #5744

@kentcdodds
Contributor

Rather than throw, I think it should generate a warning.

Ah, yes, that's right. Thanks for the reference 👍

@facebook-github-bot

@kentcdodds updated the pull request.

@zpao zpao commented on an outdated diff Jan 29, 2016
...ient/syntheticEvents/__tests__/SyntheticEvent-test.js
@@ -81,6 +81,20 @@ describe('SyntheticEvent', function() {
expect(syntheticEvent.target).toBe(null);
});
+ it('should warn when accessing properties of a destructored synthetic event', function() {
+ 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 propertie on a ' +
@zpao
zpao Jan 29, 2016 Member

"property" :)

@zpao
Member
zpao commented Jan 29, 2016

https://github.com/facebook/react/blob/master/src/shared/utils/PooledClass.js#L102-L111 is another place to look. That's what get's run to add pooling to a class, generating a new class with a static release method which calls the destructor (as @gaearon linked to).

@gaearon
Member
gaearon commented Jan 29, 2016

Another thing is you might want to put the warning code into a “devtool” which is a new work-in-progress API for doing dev-only things. See 251d6c3 and #5590 for inspiration.

@zpao
Member
zpao commented Jan 29, 2016

Might be tricky as a "devtool" since you need to add getters, which doesn't fit so well into the devtool event framework (at least as I understand it). Definitely work looking into though

@kentcdodds
Contributor

Updated. This is technically working, but there are some potential issues that I'll add some inline comments about.

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...ient/syntheticEvents/__tests__/SyntheticEvent-test.js
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`
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

When calling preventDefault, we set the defaultPrevented access the nativeEvent properties. This leads to three warnings even if the developer only called preventDefault.

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...ient/syntheticEvents/__tests__/SyntheticEvent-test.js
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`
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Similar situation as mentioned above

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...ient/syntheticEvents/__tests__/SyntheticEvent-test.js
@@ -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() {
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

whoops :-) fixing now

@kentcdodds
Contributor

On possible suggestion is to set a property on the event called _nullified or _released and check for that before trying to access any properties.

@facebook-github-bot

@kentcdodds updated the pull request.

@kentcdodds
Contributor

Heh... Still got some work on this, I've got quite a few failing tests in the full test suite and some odd behavior in the stopPropogation test (looks like console.error is called 14 times with my warning for some reason).

@kentcdodds
Contributor

I think the problem is that when we restore an event, we need to re-defineProperty the object (only in __DEV__) otherwise it will run through my getter/setter and log the warning.

Let me know if that sounds wrong. I'll push what I've got so far for review and keep working on it

@facebook-github-bot

@kentcdodds updated the pull request.

@kentcdodds
Contributor

Great. I'm ready for feedback now. All tests are passing. I have a linting question I'll add as an inline comment. I'm not solid on this approach, so definitely willing to make changes to how things work or the style of the code. 👍

@kentcdodds kentcdodds and 1 other commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
+ for (var interfacePropName in Interface) {
+ setNull(interfacePropName);
+ }
+ otherNullableProps.forEach(setNull);
+ this._destructored = true;
+
+ 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 ' +
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

I'm getting a linting error:

190:13  error  The second argument to warning must be a string literal  react-internal/warning-and-invariant-args

I think it's because of this line. Is there a reason I can't provide the propName here? I feel like it would be useful to have it.

@gaearon
gaearon Jan 29, 2016 Member

Perhaps you're supposed to use %s there. Check out other warnings in the codebase.

@gaearon
gaearon Jan 29, 2016 Member

I'd add a note about persist() just before the link so the user doesn't overlook the solution they likely need.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

👍

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
+ 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 ' +
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Same lint error here.

@facebook-github-bot

@kentcdodds updated the pull request.

@gaearon gaearon and 1 other commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -35,6 +35,11 @@ var EventInterface = {
defaultPrevented: null,
isTrusted: null,
};
+/**
+ * These are additional properties not in the EventInterface
+ * which are nulled when destructoring a syntheticEvent instance
+ */
+var otherNullableProps = ['dispatchConfig', '_targetInst', 'nativeEvent'];
@gaearon
gaearon Jan 29, 2016 Member

dispatchConfig and _targetInst are both implementation details and are considered private fields.
I think we don't have to warn on accessing those.

This leaves us with nativeEvent which can be hardcoded as a special case below.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

👍

@gaearon gaearon and 1 other commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -90,17 +100,21 @@ function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarg
assign(SyntheticEvent.prototype, {
preventDefault: function() {
+ if (this._destructored) {
@gaearon
gaearon Jan 29, 2016 Member

As far as I know, in production, this will become an empty branch:

if (this._destructored) {
}

This is why it seems better to wrap the whole thing in __DEV__ rather than the other way around.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Ah, yeah, that makes sense.

@gaearon gaearon commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -90,17 +100,21 @@ function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarg
assign(SyntheticEvent.prototype, {
preventDefault: function() {
+ if (this._destructored) {
+ if (__DEV__) {
+ warning(
+ event,
+ '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 ' +
+ 'https://fb.me/react-event-pooling for more information.'
+ );
+ }
+ return;
@gaearon
gaearon Jan 29, 2016 Member

Placing return inside __DEV__ means the library can behave differently in development and production, depending on the code below. This can lead to hard-to-reproduce bugs. It’s fine to only log a warning in dev, but all other behavior, including early exits, should be identical.

@gaearon gaearon and 1 other commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
this.defaultPrevented = true;
var event = this.nativeEvent;
- if (__DEV__) {
@gaearon
gaearon Jan 29, 2016 Member

Can you please help me understand why it is necessary to move this block up and wrap it in if (this._destructored)? It seems to me like existence of nativeEvent was enough and still should be enough to tell whether the object was “cleared”, which is how the old code worked.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

The thing I'm trying to avoid is multiple warnings. If we try to set this.defaultPrevented after this has been destructored then we'll get a warning for that action. Similarly for accessing this.nativeEvent. So this code change was an effort to first check if it's already been destructored, if it has, don't attempt to access/set variables, log the one warning, and return.

@gaearon
gaearon Jan 29, 2016 Member

I think you can do the opposite:

  • Remove the warnings from method implementations
  • In the destructor, use Object.defineProperty to put functions-that-just-emit-warnings (and noop in production) instead of the real implementations
@gaearon gaearon and 1 other commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
var Interface = this.constructor.Interface;
- for (var propName in Interface) {
- this[propName] = null;
+ for (var interfacePropName in Interface) {
+ setNull(interfacePropName);
+ }
+ otherNullableProps.forEach(setNull);
+ this._destructored = true;
+
+ function setToNullOrWarning(propName) {
@gaearon
gaearon Jan 29, 2016 Member

I don't think we want to introduce a function (and bind it) in such a hot path such as event destructor. I think it would be better to hoist this code outside destructor and make sure that when __DEV__ is false, all code and overhead related to this warning is removed.

Currently, in production, this would compile to

destructor: function() {
  // ...
  function setToNullOrWarning(propName) { // <-- function allocation
    this[propName] = null
  }
}

As you can see it’s a performance regression from just placing this code inline, which we want to avoid.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Sounds good to me. So how about I put this function at the bottom of the file, then change the code to something like:

var Interface = this.constructor.Interface;
for (var interfacePropName in Interface) {
  setToNullOrWarning(this, interfacePropName);
}
setToNullOrWarning(this, 'nativeEvent');
this.dispatchConfig = null;
this._targetInst = null;
this._destructored = true;

Where setToNullOrWarning accepts a context and a propertyName.

@gaearon
gaearon Jan 29, 2016 Member

I can't speak for React maintainers but even a function call seems like more indirection compared to a direct assignment. It might be better to do something like

if (__DEV__) {
  function getPooledWarningPropertyDefinition() {
    // ...
  }
}

// ...

var Interface = this.constructor.Interface;
for (var interfacePropName in Interface) {
  if (__DEV__) {
    Object.defineProperty(this, interfacePropName, getPooledWarningPropertyDefinition(interfacePropName))
  } else {
    this[interfacePropName] = null;
  }
}

if (__DEV__) {
  Object.defineProperty(this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent'));
} else {
  this.nativeEvent = null;
}

This way the impact on prod is minimal.

However this is just my opinion. What I would do is to grep the code for more warning calls inside getters, and see how their codepaths differ from production codepaths. It is very likely that this particular pattern (“assign a descriptor proxy in dev and a value in prod”) already exists elsewhere in the codebase, and check how it’s usually done.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

I see what you mean. With what you're suggesting, the production code will pretty much look like it does today. Makes total sense. I'll adjust my implementation to optimize for that. Thanks!

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Unless I'm missing something obvious, putting a function declaration in an if block like that wont work. However dead code elimination would remove it anyway, so I think we're good.

@jimfb
Contributor
jimfb commented Jan 29, 2016

@zpao

Might be tricky as a "devtool" since you need to add getters, which doesn't fit so well into the devtool event framework (at least as I understand it). Definitely work looking into though

Potentially worth emitting an event whenever someone attempts to access a variable on an event object. In addition to solving this issue... That would open a bunch of doors, like having a devtool that warns if you persist an event but never subsequently read from that event, etc. It would be totally doable, if we wanted to.

Anyway, probably not worth churning this PR at this point, but @gaearon is correct that it would be good to start shifting this stuff over to the devtool, and I think the devtool could be a good fit in this case.

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
var Interface = this.constructor.Interface;
for (var propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
+ delete this[propName];
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Should we be concerned about performance implications of deleteing the properties off the instance?

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

I think I'll just put this in an if (__DEV__).

@kentcdodds
Contributor

I'm pretty sure what I've got now is solid. Feedback welcome 👍 Thanks for the help so far!

@facebook-github-bot

@kentcdodds updated the pull request.

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -195,3 +200,45 @@ SyntheticEvent.augmentClass = function(Class, Interface) {
PooledClass.addPoolingTo(SyntheticEvent, PooledClass.fourArgumentPooler);
module.exports = SyntheticEvent;
+
+/*
+ * Utility functions
+ */
+
+/**
+ * Helper to nullify syntheticEvent instance properties when destructing
+ *
+ * @param {object} SyntheticEvent
+ * @param {String} propName
+ */
+function getPooledWarningPropertyDefinition(propName, getVal) {
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

This function will be dead-code eliminated when !__DEV__ because it's only referenced in if (__DEV__) statements.

@kentcdodds kentcdodds commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
}
this.dispatchConfig = null;
this._targetInst = null;
- this.nativeEvent = null;
+ this._destructored = true;
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Just realized this property is no longer necessary. Updating now.

@kentcdodds kentcdodds commented on the diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -92,15 +102,6 @@ assign(SyntheticEvent.prototype, {
preventDefault: function() {
this.defaultPrevented = true;
var event = this.nativeEvent;
- if (__DEV__) {
@kentcdodds
kentcdodds Jan 29, 2016 Contributor

This warning now happens when the property is accessed

@facebook-github-bot

@kentcdodds updated the pull request.

@gaearon gaearon and 1 other commented on an outdated diff Jan 29, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -158,11 +150,22 @@ assign(SyntheticEvent.prototype, {
destructor: function() {
var Interface = this.constructor.Interface;
for (var propName in Interface) {
- this[propName] = null;
+ if (__DEV__) {
+ Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
+ } else {
+ this[propName] = null;
+ }
+ }
+ if (__DEV__) {
+ var noop = function() {};
@gaearon
gaearon Jan 29, 2016 Member

I think we have emptyFunction somewhere in the codebase. Can you grep for it? Better than allocating it here.

@kentcdodds
kentcdodds Jan 29, 2016 Contributor

Updated. Thanks!

@facebook-github-bot

@kentcdodds updated the pull request.

@gaearon gaearon commented on an outdated diff Jan 30, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
@@ -195,3 +198,44 @@ SyntheticEvent.augmentClass = function(Class, Interface) {
PooledClass.addPoolingTo(SyntheticEvent, PooledClass.fourArgumentPooler);
module.exports = SyntheticEvent;
+
+
+// Utility functions
@gaearon
gaearon Jan 30, 2016 Member

Nit: I don't think this comment is necessary. Do we use similar comments in the codebase?

@gaearon gaearon and 1 other commented on an outdated diff Jan 30, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
+ 'This synthetic event is reused for performance reasons. If you\'re ' +
+ 'seeing this, you\'re setting property `%s` on a ' +
+ 'released/nullified synthetic event. This is effectively a no-op. See ' +
+ 'https://fb.me/react-event-pooling for more information.',
+ propName
+ );
+ 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 `%s` on a ' +
+ 'released/nullified synthetic event. This is %s. See ' +
+ 'https://fb.me/react-event-pooling for more information.',
@gaearon
gaearon Jan 30, 2016 Member

I still think it's worth adding a sentence about persist() just before the link. If you add it, please do this for every message to keep them consistent.

@kentcdodds
kentcdodds Jan 30, 2016 Contributor

Thanks for the reminder. Forgot about that. I totally agree.

@gaearon
gaearon Jan 30, 2016 Member

Also it might be good to hoist the almost identical warning message from getter/setter into the function definition, and pass the different part as %s. In addition, it might be best to preserve the old wording (calling a method rather than accessing the property) for methods.

@kentcdodds
kentcdodds Jan 30, 2016 Contributor

In addition, it might be best to preserve the old wording (calling a method rather than accessing the property) for methods.

I considered that, however this wouldn't make sense in a scenario where they're simply getting a reference to the method:

const stop = event.stopPropogation
// maybe use stop later or something?

This would log a warning that wouldn't make sense because they're not actually calling it. I realize that's an edge case, but thought it would make the code simpler and the messaging more accurate.

Definitely willing to be overruled though :-)

@gaearon
gaearon Jan 30, 2016 Member

Ah, good point. Maybe something like "accessing a method" is neutral enough?

@kentcdodds
kentcdodds Jan 30, 2016 Contributor

That's reasonable. Updating now :-)

On Sat, Jan 30, 2016 at 1:00 PM Dan Abramov notifications@github.com
wrote:

In src/renderers/dom/client/syntheticEvents/SyntheticEvent.js
#5940 (comment):

  •    'This synthetic event is reused for performance reasons. If you\'re ' +
    
  •    'seeing this, you\'re setting property `%s` on a ' +
    
  •    'released/nullified synthetic event. This is effectively a no-op. See ' +
    
  •    'https://fb.me/react-event-pooling for more information.',
    
  •    propName
    
  •  );
    
  •  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 `%s` on a ' +
    
  •    'released/nullified synthetic event. This is %s. See ' +
    
  •    'https://fb.me/react-event-pooling for more information.',
    

Ah, good point. Maybe something like "accessing a method" is neutral
enough?


Reply to this email directly or view it on GitHub
https://github.com/facebook/react/pull/5940/files#r51350027.

@gaearon
Member
gaearon commented Jan 30, 2016

At this point I’ve given all feedback I could give, and what I see so far looks good, apart from minor nits above. Let’s wait for the maintainers to give their further comments. Thank you for contributing!

cc @jimfb

@facebook-github-bot

@kentcdodds updated the pull request.

@facebook-github-bot

@kentcdodds updated the pull request.

@jimfb jimfb and 1 other commented on an outdated diff Feb 11, 2016
...ient/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'); // accessing properties on destructored events logs warnings (tested elsewhere)
@jimfb
jimfb Feb 11, 2016 Contributor

We should still assert the warnings here. The reason being that we want to know if we start emitting some unexpected warnings. Right now, this test just swallows all warnings.

@kentcdodds
kentcdodds Feb 11, 2016 Contributor

That make sense. I felt odd spying on it and not asserting anything. Will do.

@jimfb jimfb and 1 other commented on an outdated diff Feb 11, 2016
...enderers/dom/client/syntheticEvents/SyntheticEvent.js
+ }
+
+ function get() {
+ var action = isFunction ? 'accessing the method' : 'accessing the property';
+ var result = isFunction ? 'This is a no-op function' : 'This is set to null';
+ warn(action, result);
+ return getVal;
+ }
+
+ function warn(action, result) {
+ var warningCondition = false;
+ warning(
+ warningCondition,
+ 'This synthetic event is reused for performance reasons. If you\'re seeing this,' +
+ 'you\'re %s `%s` on a released/nullified synthetic event. %s.' +
+ 'If you must keep the original synthetic event around use event.persist().' +
@jimfb
jimfb Feb 11, 2016 Contributor

coma between "around" and "use"

@kentcdodds
kentcdodds Feb 11, 2016 Contributor

👍

@jimfb
Contributor
jimfb commented Feb 11, 2016

@kentcdodds Overall, this looks great to me. A couple of nitpicks. Also, I think it would be good to add an "integration" test (ie. render a component, simulate a click event, save the event, read from the event at the end of the test, and assert the warning fires). Just to sanity check that things are working.

Otherwise, I think we're good to merge.

@kentcdodds
Contributor

Happy to write the integration test. I haven't looked into how to do that yet, but I'd appreciate it if you could point me in the right direction to do that :-) Thanks for the feedback!

@jimfb
Contributor
jimfb commented Feb 11, 2016

@kentcdodds A reasonable example is in ReactServerRendering-test.js, we have a test called "should have the correct mounting behavior". Specifically, the most interesting line is: ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));

A test would probably look something like this:

var event = null;
var instance = ReactDOM.render(<div onClick={function(e){event = e;}} />);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance));`
// TODO: assert warnings.length===0
event.nativeEvent;
// TODO: assert warnings.length===1
// TODO: assert warnings[0].contanis("error message text");
@facebook-github-bot

@kentcdodds updated the pull request.

@kentcdodds
Contributor

Hi @jimfb. Sorry this took a bit. I've updated the tests and added an integration test as you suggested. I think it's solid. One thing that I did change in response to your comments is I combined two tests. When I asserted the console output in the one test, I realized that it was pretty much identical to another. So I just merged the two into one. Let me know if you'd like to see that change.

Thanks for this opportunity to contribute! :D

@jimfb
Contributor
jimfb commented Feb 18, 2016

@kentcdodds This all looks good to me, thanks! But it looks like we broke lint (you can run locally with npm run lint or view the output here: https://travis-ci.org/facebook/react/jobs/109724188). Just fix the lint errors and do a "git commit --amend", and we should be good to go.

@jimfb jimfb added this to the 0.15 milestone Feb 18, 2016
@jimfb jimfb self-assigned this Feb 18, 2016
@kentcdodds
Contributor

(-‸ლ) thanks! The PR has been updated to fix linting.

Looking forward to my next opportunity to contribute ⭐️

@facebook-github-bot

@kentcdodds updated the pull request.

@jimfb jimfb merged commit e8e56e8 into facebook:master Feb 18, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@jimfb
Contributor
jimfb commented Feb 18, 2016

Thanks @kentcdodds!

@kentcdodds
Contributor

🎉 🎊

@kentcdodds kentcdodds deleted the kentcdodds:pr/warn-event-pool-access branch Feb 18, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment