Skip to content

Commit 94d0dc6

Browse files
ericmatthysgaearon
authored andcommitted
Do not clone key and ref getters
1 parent c0ecde6 commit 94d0dc6

File tree

2 files changed

+108
-10
lines changed

2 files changed

+108
-10
lines changed

src/isomorphic/classic/element/ReactElement.js

+28-10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ var RESERVED_PROPS = {
3232

3333
var specialPropKeyWarningShown, specialPropRefWarningShown;
3434

35+
function isValidConfigRefOrKey(config, name) {
36+
if (__DEV__) {
37+
return hasOwnProperty.call(config, name) &&
38+
!Object.getOwnPropertyDescriptor(config, name).get;
39+
}
40+
41+
return config[name] !== undefined;
42+
}
43+
44+
function getConfigKey(config) {
45+
return '' + config.key;
46+
}
47+
3548
/**
3649
* Factory method to create a new React element. This no longer adheres to
3750
* the class pattern, so do not use new to call it. Also, no instanceof check
@@ -138,14 +151,16 @@ ReactElement.createElement = function(type, config, children) {
138151
'React.createElement(...): Expected props argument to be a plain object. ' +
139152
'Properties defined in its prototype chain will be ignored.'
140153
);
141-
ref = !hasOwnProperty.call(config, 'ref') ||
142-
Object.getOwnPropertyDescriptor(config, 'ref').get ? null : config.ref;
143-
key = !hasOwnProperty.call(config, 'key') ||
144-
Object.getOwnPropertyDescriptor(config, 'key').get ? null : '' + config.key;
145-
} else {
146-
ref = config.ref === undefined ? null : config.ref;
147-
key = config.key === undefined ? null : '' + config.key;
148154
}
155+
156+
if (isValidConfigRefOrKey(config, 'ref')) {
157+
ref = config.ref;
158+
}
159+
160+
if (isValidConfigRefOrKey(config, 'key')) {
161+
key = getConfigKey(config);
162+
}
163+
149164
self = config.__self === undefined ? null : config.__self;
150165
source = config.__source === undefined ? null : config.__source;
151166
// Remaining properties are added to a new props object
@@ -297,14 +312,17 @@ ReactElement.cloneElement = function(element, config, children) {
297312
'Properties defined in its prototype chain will be ignored.'
298313
);
299314
}
300-
if (config.ref !== undefined) {
315+
316+
if (isValidConfigRefOrKey(config, 'ref')) {
301317
// Silently steal the ref from the parent.
302318
ref = config.ref;
303319
owner = ReactCurrentOwner.current;
304320
}
305-
if (config.key !== undefined) {
306-
key = '' + config.key;
321+
322+
if (isValidConfigRefOrKey(config, 'key')) {
323+
key = getConfigKey(config);
307324
}
325+
308326
// Remaining properties override existing props
309327
var defaultProps;
310328
if (element.type && element.type.defaultProps) {

src/isomorphic/classic/element/__tests__/ReactElement-test.js

+80
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,86 @@ describe('ReactElement', function() {
170170
expect(element.props).toEqual(expectation);
171171
});
172172

173+
it('should not extract key and ref getters from the config when creating an element', function() {
174+
var props = {
175+
foo: '56',
176+
};
177+
178+
Object.defineProperty(props, 'key', {
179+
get: function() {
180+
return '12';
181+
},
182+
});
183+
184+
Object.defineProperty(props, 'ref', {
185+
get: function() {
186+
return '34';
187+
},
188+
});
189+
190+
var element = React.createFactory(ComponentClass)(props);
191+
expect(element.type).toBe(ComponentClass);
192+
expect(element.key).toBe(null);
193+
expect(element.ref).toBe(null);
194+
var expectation = {foo:'56'};
195+
Object.freeze(expectation);
196+
expect(element.props).toEqual(expectation);
197+
});
198+
199+
it('should not extract key and ref getters from the config when cloning an element', function() {
200+
var element = React.createFactory(ComponentClass)({
201+
key: '12',
202+
ref: '34',
203+
foo: '56',
204+
});
205+
206+
var props = {
207+
foo: 'ef',
208+
};
209+
210+
Object.defineProperty(props, 'key', {
211+
get: function() {
212+
return 'ab';
213+
},
214+
});
215+
216+
Object.defineProperty(props, 'ref', {
217+
get: function() {
218+
return 'cd';
219+
},
220+
});
221+
222+
var clone = React.cloneElement(element, props);
223+
expect(clone.type).toBe(ComponentClass);
224+
expect(clone.key).toBe('12');
225+
expect(clone.ref).toBe('34');
226+
var expectation = {foo:'ef'};
227+
Object.freeze(expectation);
228+
expect(clone.props).toEqual(expectation);
229+
});
230+
231+
it('should allow null key and ref values when cloning an element', function() {
232+
var element = React.createFactory(ComponentClass)({
233+
key: '12',
234+
ref: '34',
235+
foo: '56',
236+
});
237+
238+
var props = {
239+
key: null,
240+
ref: null,
241+
foo: 'ef',
242+
};
243+
244+
var clone = React.cloneElement(element, props);
245+
expect(clone.type).toBe(ComponentClass);
246+
expect(clone.key).toBe('null');
247+
expect(clone.ref).toBe(null);
248+
var expectation = {foo:'ef'};
249+
Object.freeze(expectation);
250+
expect(clone.props).toEqual(expectation);
251+
});
252+
173253
it('coerces the key to a string', function() {
174254
var element = React.createFactory(ComponentClass)({
175255
key: 12,

0 commit comments

Comments
 (0)