diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index b66cf4900e..40a94b8bed 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -736,10 +736,11 @@ var ReactCompositeComponentMixin = { this._defaultProps = null; - ReactComponent.Mixin.unmountComponent.call(this); this._renderedComponent.unmountComponent(); this._renderedComponent = null; + ReactComponent.Mixin.unmountComponent.call(this); + if (this.refs) { this.refs = null; } diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index 44e983510f..08de4eb2d8 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -380,9 +380,9 @@ ReactDOMComponent.Mixin = { * @internal */ unmountComponent: function() { + this.unmountChildren(); ReactEventEmitter.deleteAllListeners(this._rootNodeID); ReactComponent.Mixin.unmountComponent.call(this); - this.unmountChildren(); } }; diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 1f2f8dd52b..ed75168de4 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -22,6 +22,7 @@ var MorphingComponent; var ChildUpdates; var React; +var ReactComponent; var ReactCurrentOwner; var ReactPropTypes; var ReactTestUtils; @@ -40,6 +41,7 @@ describe('ReactCompositeComponent', function() { reactComponentExpect = require('reactComponentExpect'); React = require('React'); + ReactComponent = require('ReactComponent'); ReactCurrentOwner = require('ReactCurrentOwner'); ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated'); ReactPropTypes = require('ReactPropTypes'); @@ -583,6 +585,42 @@ describe('ReactCompositeComponent', function() { }); }); + it('should call componentWillUnmount before unmounting', function() { + var container = document.createElement('div'); + var innerUnmounted = false; + + spyOn(ReactMount, 'purgeID').andCallThrough(); + + var Component = React.createClass({ + render: function() { + return
+ +
; + } + }); + var Inner = React.createClass({ + componentWillUnmount: function() { + // It's important that ReactMount.purgeID be called after any component + // lifecycle methods, because a componentWillMount implementation is + // likely call this.getDOMNode(), which will repopulate the node cache + // after it's been cleared, causing a memory leak. + expect(ReactMount.purgeID.callCount).toBe(0); + innerUnmounted = true; + }, + render: function() { + return
; + } + }); + + React.renderComponent(, container); + React.unmountComponentAtNode(container); + expect(innerUnmounted).toBe(true); + + // , , and both
elements each call + // unmountIDFromEnvironment which calls purgeID, for a total of 4. + expect(ReactMount.purgeID.callCount).toBe(4); + }); + it('should detect valid CompositeComponent classes', function() { var Component = React.createClass({ render: function() {