Unbreak refs
If no refs are rendered, `this.refs` is undefined. This is bad since it deopts & is hard to look for. Instead we should make `this.refs` an immutable empty object.
Pete Hunt committed
Mar 3, 2014 at 09:41 UTC
6666538316c0a7384b4ceca01aa364c9f615c9d0
4 files changed
+26
-6
src/core/ReactCompositeComponent.js
+2
-4
@@ -767,6 +767,7 @@ var ReactCompositeComponentMixin = {
767
construct: function(initialProps, children) {
768
// Children can be either an array or more than one argument
769
ReactComponent.Mixin.construct.apply(this, arguments);
770
+ ReactOwner.Mixin.construct.apply(this, arguments);
771
772
this.state = null;
773
this._pendingState = null;
@@ -873,10 +874,7 @@ var ReactCompositeComponentMixin = {
874
this._renderedComponent = null;
875
876
ReactComponent.Mixin.unmountComponent.call(this);
876
-
877
- if (this.refs) {
878
- this.refs = null;
879
- }
877
+ ReactOwner.Mixin.unmountComponent.call(this);
878
879
// Some existing components rely on this.props even after they've been
880
// destroyed (in event handlers).
src/core/ReactOwner.js
+10
-1
@@ -18,6 +18,7 @@
18
19
"use strict";
20
21
+var emptyObject = require('emptyObject');
22
var invariant = require('invariant');
23
24
/**
@@ -118,6 +119,10 @@ var ReactOwner = {
119
*/
120
Mixin: {
121
122
+ construct: function() {
123
+ this.refs = emptyObject;
124
+ },
125
+
126
/**
127
* Lazily allocates the refs object and stores `component` as `ref`.
128
*
@@ -132,7 +137,7 @@ var ReactOwner = {
137
'attachRef(%s, ...): Only a component\'s owner can store a ref to it.',
138
ref
139
);
135
- var refs = this.refs || (this.refs = {});
140
+ var refs = this.refs === emptyObject ? (this.refs = {}) : this.refs;
141
refs[ref] = component;
142
},
143
@@ -145,6 +150,10 @@ var ReactOwner = {
150
*/
151
detachRef: function(ref) {
152
delete this.refs[ref];
153
+ },
154
+
155
+ unmountComponent: function() {
156
+ this.refs = null;
157
}
158
159
}
src/core/__tests__/refs-test.js
+12
@@ -236,5 +236,17 @@ describe('ref swapping', function() {
236
expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
237
expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
238
});
239
+
240
+
241
+ it('always has a value for this.refs', function() {
242
+ var Component = React.createClass({
243
+ render: function() {
244
+ return <div />;
245
+ }
246
+ });
247
+
248
+ var instance = ReactTestUtils.renderIntoDocument(<Component />);
249
+ expect(!!instance.refs).toBe(true);
250
+ });
251
});
252
src/utils/__tests__/cloneWithProps-test.js
+2
-1
@@ -24,6 +24,7 @@ require('mock-modules').dontMock('cloneWithProps');
24
var mocks = require('mocks');
25
26
var cloneWithProps = require('cloneWithProps');
27
+var emptyObject = require('emptyObject');
28
29
var React;
30
var ReactTestUtils;
@@ -107,7 +108,7 @@ describe('cloneWithProps', function() {
108
console.warn = mocks.getMockFunction();
109
110
var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
110
- expect(component.refs).toBe(undefined);
111
+ expect(component.refs).toBe(emptyObject);
112
expect(console.warn.mock.calls.length).toBe(1);
113
} finally {
114
console.warn = _warn;