mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add component stack to invalid element type warning (#8495)
* Show Source Error Addemden if __source available * Add Parent Stack on invalid element type * refactor to use normalizeCodeLocInfo * Remove ( ) from addendum
This commit is contained in:
@@ -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 ' +
|
||||
|
||||
@@ -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 <Foo>{[<div />]}</Foo>;
|
||||
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 **.'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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(<ParentComp />);
|
||||
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 <MyComp color={props.color} />;
|
||||
}
|
||||
function ParentComp(props) {
|
||||
if (props.warn) {
|
||||
// This element has a source thanks to JSX.
|
||||
return <MiddleComp color={42} />;
|
||||
}
|
||||
// This element has no source.
|
||||
return React.createElement(MiddleComp, {color: 'blue'});
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<ParentComp warn={false} />, container);
|
||||
ReactDOM.render(<ParentComp warn={true} />, 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 <True />;
|
||||
void <Num />;
|
||||
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 <Div />;
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
@@ -251,9 +296,7 @@ describe('ReactJSXElementValidator', () => {
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
|
||||
|
||||
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(<RequiredPropComponent prop={null} />);
|
||||
|
||||
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(<RequiredPropComponent prop={42} />);
|
||||
|
||||
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' +
|
||||
|
||||
Reference in New Issue
Block a user