diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js
index e7ad9d72a7..6613aeeb61 100644
--- a/src/isomorphic/classic/element/ReactElementValidator.js
+++ b/src/isomorphic/classic/element/ReactElementValidator.js
@@ -38,6 +38,20 @@ function getDeclarationErrorAddendum() {
return '';
}
+function getSourceInfoErrorAddendum(elementProps) {
+ if (
+ elementProps !== null &&
+ elementProps !== undefined &&
+ elementProps.__source !== undefined
+ ) {
+ var source = elementProps.__source;
+ var fileName = source.fileName.replace(/^.*[\\\/]/, '');
+ var lineNumber = source.lineNumber;
+ return ' Check your code at ' + fileName + ':' + lineNumber + '.';
+ }
+ return '';
+}
+
/**
* Warn if there's no key explicitly set on dynamic arrays of children or
* object keys are not valid. This allows us to keep track of children between
@@ -202,7 +216,16 @@ var ReactElementValidator = {
' You likely forgot to export your component from the file ' +
'it\'s defined in.';
}
- info += getDeclarationErrorAddendum();
+
+ var sourceInfo = getSourceInfoErrorAddendum(props);
+ if (sourceInfo) {
+ info += sourceInfo;
+ } else {
+ info += getDeclarationErrorAddendum();
+ }
+
+ info += ReactComponentTreeHook.getCurrentStackAddendum();
+
warning(
false,
'React.createElement: type is invalid -- expected a string (for ' +
diff --git a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
index 17367f0439..f2e403c5a4 100644
--- a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
+++ b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
@@ -20,7 +20,7 @@ var ReactTestUtils;
describe('ReactElementValidator', () => {
function normalizeCodeLocInfo(str) {
- return str.replace(/\(at .+?:\d+\)/g, '(at **)');
+ return str && str.replace(/at .+?:\d+/g, 'at **');
}
var ComponentClass;
@@ -334,7 +334,8 @@ describe('ReactElementValidator', () => {
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
- 'components) but got: null. Check the render method of `ParentComp`.'
+ 'components) but got: null. Check the render method of `ParentComp`.' +
+ '\n in ParentComp'
);
});
@@ -526,11 +527,11 @@ describe('ReactElementValidator', () => {
var Foo = undefined;
void {[]};
expect(console.error.calls.count()).toBe(1);
- expect(console.error.calls.argsFor(0)[0]).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: undefined. You likely forgot to export your ' +
- 'component from the file it\'s defined in.'
+ 'component from the file it\'s defined in. Check your code at **.'
);
});
diff --git a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
index 00f3650217..5bb44c6943 100644
--- a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
+++ b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
@@ -15,9 +15,14 @@
// of dynamic errors when using JSX with Flow.
var React;
+var ReactDOM;
var ReactTestUtils;
describe('ReactJSXElementValidator', () => {
+ function normalizeCodeLocInfo(str) {
+ return str && str.replace(/at .+?:\d+/g, 'at **');
+ }
+
var Component;
var RequiredPropComponent;
@@ -25,6 +30,7 @@ describe('ReactJSXElementValidator', () => {
jest.resetModuleRegistry();
React = require('React');
+ ReactDOM = require('ReactDOM');
ReactTestUtils = require('ReactTestUtils');
Component = class extends React.Component {
@@ -195,9 +201,7 @@ describe('ReactJSXElementValidator', () => {
}
}
ReactTestUtils.renderIntoDocument();
- expect(
- console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
- ).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
@@ -206,6 +210,43 @@ describe('ReactJSXElementValidator', () => {
);
});
+ it('should update component stack after receiving next element', () => {
+ spyOn(console, 'error');
+ function MyComp() {
+ return null;
+ }
+ MyComp.propTypes = {
+ color: React.PropTypes.string,
+ };
+ function MiddleComp(props) {
+ return ;
+ }
+ function ParentComp(props) {
+ if (props.warn) {
+ // This element has a source thanks to JSX.
+ return ;
+ }
+ // This element has no source.
+ return React.createElement(MiddleComp, {color: 'blue'});
+ }
+
+ var container = document.createElement('div');
+ ReactDOM.render(, container);
+ ReactDOM.render(, container);
+
+ expect(console.error.calls.count()).toBe(1);
+ // The warning should have the full stack with line numbers.
+ // If it doesn't, it means we're using information from the old element.
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
+ 'Warning: Failed prop type: ' +
+ 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
+ 'expected `string`.\n' +
+ ' in MyComp (at **)\n' +
+ ' in MiddleComp (at **)\n' +
+ ' in ParentComp (at **)'
+ );
+ });
+
it('gives a helpful error when passing null, undefined, or boolean', () => {
var Undefined = undefined;
var Null = null;
@@ -218,26 +259,30 @@ describe('ReactJSXElementValidator', () => {
void ;
void ;
expect(console.error.calls.count()).toBe(4);
- expect(console.error.calls.argsFor(0)[0]).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: undefined. You likely forgot to export your ' +
- 'component from the file it\'s defined in.'
+ 'component from the file it\'s defined in. ' +
+ 'Check your code at **.'
);
- expect(console.error.calls.argsFor(1)[0]).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
- 'components) but got: null.'
+ 'components) but got: null. ' +
+ 'Check your code at **.'
);
- expect(console.error.calls.argsFor(2)[0]).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
- 'components) but got: boolean.'
+ 'components) but got: boolean. ' +
+ 'Check your code at **.'
);
- expect(console.error.calls.argsFor(3)[0]).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(3)[0])).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
- 'components) but got: number.'
+ 'components) but got: number. ' +
+ 'Check your code at **.'
);
void
;
expect(console.error.calls.count()).toBe(4);
@@ -251,9 +296,7 @@ describe('ReactJSXElementValidator', () => {
ReactTestUtils.renderIntoDocument();
expect(console.error.calls.count()).toBe(1);
- expect(
- console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
- ).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`RequiredPropComponent`, but its value is `null`.\n' +
' in RequiredPropComponent (at **)'
@@ -266,9 +309,7 @@ describe('ReactJSXElementValidator', () => {
ReactTestUtils.renderIntoDocument();
expect(console.error.calls.count()).toBe(1);
- expect(
- console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
- ).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`RequiredPropComponent`, but its value is `null`.\n' +
' in RequiredPropComponent (at **)'
@@ -282,18 +323,14 @@ describe('ReactJSXElementValidator', () => {
ReactTestUtils.renderIntoDocument();
expect(console.error.calls.count()).toBe(2);
- expect(
- console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
- ).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Failed prop type: ' +
'The prop `prop` is marked as required in `RequiredPropComponent`, but ' +
'its value is `undefined`.\n' +
' in RequiredPropComponent (at **)'
);
- expect(
- console.error.calls.argsFor(1)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
- ).toBe(
+ expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
'Warning: Failed prop type: ' +
'Invalid prop `prop` of type `number` supplied to ' +
'`RequiredPropComponent`, expected `string`.\n' +