Improve error message when mounting non-string/function elements
Ben Alpert committed
Jun 17, 2015 at 00:16 UTC
642323e5a8ad814e372b67bd76ded2d6d5bd383d
3 files changed
+52
-12
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
+6
-5
@@ -327,17 +327,18 @@ describe('ReactElementValidator', function() {
327
});
328
expect(function() {
329
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
330
- }).toThrow();
331
- expect(console.error.calls.length).toBe(2);
330
+ }).toThrow(
331
+ 'Invariant Violation: Element type is invalid: expected a string (for ' +
332
+ 'built-in components) or a class/function (for composite components) ' +
333
+ 'but got: null. Check the render method of `ParentComp`.'
334
+ );
335
+ expect(console.error.calls.length).toBe(1);
336
expect(console.error.calls[0].args[0]).toBe(
337
'Warning: React.createElement: type should not be null, undefined, ' +
338
'boolean, or number. It should be a string (for DOM elements) or a ' +
339
'ReactClass (for composite components). Check the render method of ' +
340
'`ParentComp`.'
341
);
338
- expect(console.error.calls[1].args[0]).toBe(
339
- 'Warning: Only functions or strings can be mounted as React components.'
340
- );
342
});
343
344
it('should check default prop values', function() {
src/renderers/shared/reconciler/__tests__/ReactComponent-test.js
+28
@@ -279,4 +279,32 @@ describe('ReactComponent', function() {
279
);
280
});
281
282
+ it('throws usefully when rendering badly-typed elements', function() {
283
+ spyOn(console, 'error');
284
+
285
+ var X = undefined;
286
+ expect(() => ReactTestUtils.renderIntoDocument(<X />)).toThrow(
287
+ 'Invariant Violation: Element type is invalid: expected a string (for ' +
288
+ 'built-in components) or a class/function (for composite components) ' +
289
+ 'but got: undefined.'
290
+ );
291
+
292
+ var Y = null;
293
+ expect(() => ReactTestUtils.renderIntoDocument(<Y />)).toThrow(
294
+ 'Invariant Violation: Element type is invalid: expected a string (for ' +
295
+ 'built-in components) or a class/function (for composite components) ' +
296
+ 'but got: null.'
297
+ );
298
+
299
+ var Z = {};
300
+ expect(() => ReactTestUtils.renderIntoDocument(<Z />)).toThrow(
301
+ 'Invariant Violation: Element type is invalid: expected a string (for ' +
302
+ 'built-in components) or a class/function (for composite components) ' +
303
+ 'but got: object.'
304
+ );
305
+
306
+ // One warning for each element creation
307
+ expect(console.error.calls.length).toBe(3);
308
+ });
309
+
310
});
src/renderers/shared/reconciler/instantiateReactComponent.js
+18
-7
@@ -30,6 +30,16 @@ assign(
30
}
31
);
32
33
+function getDeclarationErrorAddendum(owner) {
34
+ if (owner) {
35
+ var name = owner.getName();
36
+ if (name) {
37
+ return ' Check the render method of `' + name + '`.';
38
+ }
39
+ }
40
+ return '';
41
+}
42
+
43
/**
44
* Check if the type reference is a known internal type. I.e. not a user
45
* provided composite type.
@@ -63,13 +73,14 @@ function instantiateReactComponent(node, parentCompositeType) {
73
74
if (typeof node === 'object') {
75
var element = node;
66
- if (__DEV__) {
67
- warning(
68
- element && (typeof element.type === 'function' ||
69
- typeof element.type === 'string'),
70
- 'Only functions or strings can be mounted as React components.'
71
- );
72
- }
76
+ invariant(
77
+ element && (typeof element.type === 'function' ||
78
+ typeof element.type === 'string'),
79
+ 'Element type is invalid: expected a string (for built-in components) ' +
80
+ 'or a class/function (for composite components) but got: %s.%s',
81
+ element.type == null ? element.type : typeof element.type,
82
+ getDeclarationErrorAddendum(element._owner)
83
+ );
84
85
// Special case string values
86
if (parentCompositeType === element.type &&