Start warning when React.DOM.x is passed into JSX
React.DOM is becoming helper factories to generate ReactElements. They're not classes. It will be ok to call them directly as functions, but not to use them where a class is expected.
Sebastian Markbage committed
Oct 7, 2014 at 10:26 UTC
7f9b1d18d197d102777f214e782ecc27685cf481
8 files changed
+86
-29
src/browser/ReactDOM.js
+2
-2
@@ -33,11 +33,11 @@ var mapObject = require('mapObject');
33
*/
34
function createDOMFactory(tag) {
35
if (__DEV__) {
36
- return ReactLegacyDescriptor.wrapFactory(
36
+ return ReactLegacyDescriptor.markNonLegacyFactory(
37
ReactDescriptorValidator.createFactory(tag)
38
);
39
}
40
- return ReactLegacyDescriptor.wrapFactory(
40
+ return ReactLegacyDescriptor.markNonLegacyFactory(
41
ReactDescriptor.createFactory(tag)
42
);
43
}
src/browser/__tests__/ReactDOM-test.js
+29
-2
@@ -25,7 +25,7 @@ var React = require('React');
25
var ReactDOM = require('ReactDOM');
26
var ReactMount = require('ReactMount');
27
var ReactTestUtils = require('ReactTestUtils');
28
-var div = React.createFactory(ReactDOM.div); // TODO: use string
28
+var div = React.createFactory('div');
29
30
describe('ReactDOM', function() {
31
// TODO: uncomment this test once we can run in phantom, which
@@ -117,6 +117,33 @@ describe('ReactDOM', function() {
117
});
118
119
it('should be a valid class', function() {
120
- expect(React.isValidClass(ReactDOM.div)).toBe(true);
120
+ expect(React.isValidClass(ReactDOM.div)).toBe(false);
121
+ });
122
+
123
+ it('allow React.DOM factories to be called without warnings', function() {
124
+ spyOn(console, 'warn');
125
+ var descriptor = React.DOM.div();
126
+ expect(descriptor.type).toBe('div');
127
+ expect(console.warn.argsForCall.length).toBe(0);
128
+ });
129
+
130
+ it('warns but allow dom factories to be used in createFactory', function() {
131
+ spyOn(console, 'warn');
132
+ var factory = React.createFactory(React.DOM.div);
133
+ expect(factory().type).toBe('div');
134
+ expect(console.warn.argsForCall.length).toBe(1);
135
+ expect(console.warn.argsForCall[0][0]).toContain(
136
+ 'Do not pass React.DOM.div'
137
+ );
138
+ });
139
+
140
+ it('warns but allow dom factories to be used in createElement', function() {
141
+ spyOn(console, 'warn');
142
+ var descriptor = React.createElement(React.DOM.div);
143
+ expect(descriptor.type).toBe('div');
144
+ expect(console.warn.argsForCall.length).toBe(1);
145
+ expect(console.warn.argsForCall[0][0]).toContain(
146
+ 'Do not pass React.DOM.div'
147
+ );
148
});
149
});
src/browser/ui/React.js
+1
-1
@@ -90,7 +90,7 @@ var React = {
90
renderComponentToStaticMarkup:
91
ReactServerRendering.renderComponentToStaticMarkup,
92
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
93
- isValidClass: ReactDescriptor.isValidFactory,
93
+ isValidClass: ReactLegacyDescriptor.isValidFactory,
94
isValidComponent: ReactDescriptor.isValidDescriptor,
95
withContext: ReactContext.withContext,
96
__internals: {
src/browser/ui/ReactMount.js
+1
-1
@@ -348,7 +348,7 @@ var ReactMount = {
348
typeof nextDescriptor === 'string' ?
349
' Instead of passing an element string, make sure to instantiate ' +
350
'it by passing it to React.createElement.' :
351
- ReactDescriptor.isValidFactory(nextDescriptor) ?
351
+ ReactLegacyDescriptor.isValidFactory(nextDescriptor) ?
352
' Instead of passing a component class, make sure to instantiate ' +
353
'it by passing it to React.createElement.' :
354
// Check if it quacks like a descriptor
src/core/ReactCompositeComponent.js
+1
-1
@@ -471,7 +471,7 @@ function mixSpecIntoComponent(Constructor, spec) {
471
}
472
473
invariant(
474
- !ReactDescriptor.isValidFactory(spec),
474
+ !ReactLegacyDescriptor.isValidFactory(spec),
475
'ReactCompositeComponent: You\'re attempting to ' +
476
'use a component class as a mixin. Instead, just use a regular object.'
477
);
src/core/ReactDescriptor.js
-17
@@ -214,23 +214,6 @@ ReactDescriptor.cloneAndReplaceProps = function(oldDescriptor, newProps) {
214
return newDescriptor;
215
};
216
217
-/**
218
- * Checks if a value is a valid descriptor constructor.
219
- *
220
- * @param {*}
221
- * @return {boolean}
222
- * @public
223
- */
224
-ReactDescriptor.isValidFactory = function(factory) {
225
- return typeof factory === 'function' && (
226
- typeof factory.type === 'string' || (
227
- typeof factory.type === 'function' &&
228
- typeof factory.type.prototype.mountComponent === 'function' &&
229
- typeof factory.type.prototype.receiveComponent === 'function'
230
- )
231
- );
232
-};
233
-
217
/**
218
* @param {?object} object
219
* @return {boolean} True if `object` is a valid component.
src/core/ReactLegacyDescriptor.js
+51
-3
@@ -19,7 +19,6 @@
19
"use strict";
20
21
var ReactCurrentOwner = require('ReactCurrentOwner');
22
-var ReactDescriptor = require('ReactDescriptor');
22
23
var invariant = require('invariant');
24
var monitorCodeUse = require('monitorCodeUse');
@@ -78,6 +77,14 @@ function warnForPlainFunctionType(type) {
77
}
78
}
79
80
+function warnForNonLegacyFactory(type) {
81
+ warning(
82
+ false,
83
+ 'Do not pass React.DOM.' + type.type + ' to JSX or createFactory. ' +
84
+ 'Use the string "' + type + '" instead.'
85
+ );
86
+}
87
+
88
/**
89
* Transfer static properties from the source to the target. Functions are
90
* rebound to have this reflect the original source.
@@ -109,6 +116,7 @@ function proxyStaticMethods(target, source) {
116
// We use an object instead of a boolean because booleans are ignored by our
117
// mocking libraries when these factories gets mocked.
118
var LEGACY_MARKER = {};
119
+var NON_LEGACY_MARKER = {};
120
121
var ReactLegacyDescriptorFactory = {};
122
@@ -119,6 +127,16 @@ ReactLegacyDescriptorFactory.wrapCreateFactory = function(createFactory) {
127
return createFactory(type);
128
}
129
130
+ if (type.isReactNonLegacyFactory) {
131
+ // This is probably a factory created by ReactDOM we unwrap it to get to
132
+ // the underlying string type. It shouldn't have been passed here so we
133
+ // warn.
134
+ if (__DEV__) {
135
+ warnForNonLegacyFactory(type);
136
+ }
137
+ return createFactory(type.type);
138
+ }
139
+
140
if (type.isReactLegacyFactory) {
141
// This is probably a legacy factory created by ReactCompositeComponent.
142
// We unwrap it to get to the underlying class.
@@ -143,6 +161,20 @@ ReactLegacyDescriptorFactory.wrapCreateDescriptor = function(createDescriptor) {
161
return createDescriptor.apply(this, arguments);
162
}
163
164
+ var args;
165
+
166
+ if (type.isReactNonLegacyFactory) {
167
+ // This is probably a factory created by ReactDOM we unwrap it to get to
168
+ // the underlying string type. It shouldn't have been passed here so we
169
+ // warn.
170
+ if (__DEV__) {
171
+ warnForNonLegacyFactory(type);
172
+ }
173
+ args = Array.prototype.slice.call(arguments, 0);
174
+ args[0] = type.type;
175
+ return createDescriptor.apply(this, args);
176
+ }
177
+
178
if (type.isReactLegacyFactory) {
179
// This is probably a legacy factory created by ReactCompositeComponent.
180
// We unwrap it to get to the underlying class.
@@ -152,7 +184,7 @@ ReactLegacyDescriptorFactory.wrapCreateDescriptor = function(createDescriptor) {
184
// future proofs unit testing that assume that these are classes.
185
type.type._mockedReactClassConstructor = type;
186
}
155
- var args = Array.prototype.slice.call(arguments, 0);
187
+ args = Array.prototype.slice.call(arguments, 0);
188
args[0] = type.type;
189
return createDescriptor.apply(this, args);
190
}
@@ -170,7 +202,7 @@ ReactLegacyDescriptorFactory.wrapCreateDescriptor = function(createDescriptor) {
202
203
ReactLegacyDescriptorFactory.wrapFactory = function(factory) {
204
invariant(
173
- ReactDescriptor.isValidFactory(factory),
205
+ typeof factory === 'function',
206
'This is suppose to accept a descriptor factory'
207
);
208
var legacyDescriptorFactory = function(config, children) {
@@ -186,6 +218,22 @@ ReactLegacyDescriptorFactory.wrapFactory = function(factory) {
218
return legacyDescriptorFactory;
219
};
220
221
+// This is used to mark a factory that will remain. E.g. we're allowed to call
222
+// it as a function. However, you're not suppose to pass it to createElement
223
+// or createFactory, so it will warn you if you do.
224
+ReactLegacyDescriptorFactory.markNonLegacyFactory = function(factory) {
225
+ factory.isReactNonLegacyFactory = NON_LEGACY_MARKER;
226
+ return factory;
227
+};
228
+
229
+// Checks if a factory function is actually a legacy factory pretending to
230
+// be a class.
231
+ReactLegacyDescriptorFactory.isValidFactory = function(factory) {
232
+ // TODO: This will be removed and moved into a class validator or something.
233
+ return typeof factory === 'function' &&
234
+ factory.isReactLegacyFactory === LEGACY_MARKER;
235
+};
236
+
237
ReactLegacyDescriptorFactory._isLegacyCallWarningEnabled = true;
238
239
module.exports = ReactLegacyDescriptorFactory;
src/test/ReactTestUtils.js
+1
-2
@@ -23,7 +23,6 @@ var EventPluginHub = require('EventPluginHub');
23
var EventPropagators = require('EventPropagators');
24
var React = require('React');
25
var ReactDescriptor = require('ReactDescriptor');
26
-var ReactDOM = require('ReactDOM');
26
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
27
var ReactMount = require('ReactMount');
28
var ReactTextComponent = require('ReactTextComponent');
@@ -242,7 +241,7 @@ var ReactTestUtils = {
241
var ConvenienceConstructor = React.createClass({
242
render: function() {
243
return React.createElement(
245
- ReactDOM[mockTagName], // TODO: Replace this with just a string
244
+ mockTagName,
245
null,
246
this.props.children
247
);