mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Improve validateDOMNesting message for whitespace (#7515)
For #5071.
(cherry picked from commit 6a65960641)
This commit is contained in:
committed by
Paul O’Shannessy
parent
8898803b4a
commit
4be377650e
@@ -322,11 +322,24 @@ if (__DEV__) {
|
||||
|
||||
var didWarn = {};
|
||||
|
||||
validateDOMNesting = function(childTag, childInstance, ancestorInfo) {
|
||||
validateDOMNesting = function(
|
||||
childTag,
|
||||
childText,
|
||||
childInstance,
|
||||
ancestorInfo
|
||||
) {
|
||||
ancestorInfo = ancestorInfo || emptyAncestorInfo;
|
||||
var parentInfo = ancestorInfo.current;
|
||||
var parentTag = parentInfo && parentInfo.tag;
|
||||
|
||||
if (childText != null) {
|
||||
warning(
|
||||
childTag == null,
|
||||
'validateDOMNesting: when childText is passed, childTag should be null'
|
||||
);
|
||||
childTag = '#text';
|
||||
}
|
||||
|
||||
var invalidParent =
|
||||
isTagValidWithParent(childTag, parentTag) ? null : parentInfo;
|
||||
var invalidAncestor =
|
||||
@@ -385,7 +398,17 @@ if (__DEV__) {
|
||||
didWarn[warnKey] = true;
|
||||
|
||||
var tagDisplayName = childTag;
|
||||
if (childTag !== '#text') {
|
||||
var whitespaceInfo = '';
|
||||
if (childTag === '#text') {
|
||||
if (/\S/.test(childText)) {
|
||||
tagDisplayName = 'Text nodes';
|
||||
} else {
|
||||
tagDisplayName = 'Whitespace text nodes';
|
||||
whitespaceInfo =
|
||||
' Make sure you don\'t have any extra whitespace between tags on ' +
|
||||
'each line of your source code.';
|
||||
}
|
||||
} else {
|
||||
tagDisplayName = '<' + childTag + '>';
|
||||
}
|
||||
|
||||
@@ -398,10 +421,11 @@ if (__DEV__) {
|
||||
}
|
||||
warning(
|
||||
false,
|
||||
'validateDOMNesting(...): %s cannot appear as a child of <%s>. ' +
|
||||
'validateDOMNesting(...): %s cannot appear as a child of <%s>.%s ' +
|
||||
'See %s.%s',
|
||||
tagDisplayName,
|
||||
ancestorTag,
|
||||
whitespaceInfo,
|
||||
ownerInfo,
|
||||
info
|
||||
);
|
||||
|
||||
@@ -253,9 +253,9 @@ function optionPostMount() {
|
||||
ReactDOMOption.postMountWrapper(inst);
|
||||
}
|
||||
|
||||
var setContentChildForInstrumentation = emptyFunction;
|
||||
var setAndValidateContentChildDev = emptyFunction;
|
||||
if (__DEV__) {
|
||||
setContentChildForInstrumentation = function(content) {
|
||||
setAndValidateContentChildDev = function(content) {
|
||||
var hasExistingContent = this._contentDebugID != null;
|
||||
var debugID = this._debugID;
|
||||
// This ID represents the inlined child that has no backing instance:
|
||||
@@ -269,6 +269,7 @@ if (__DEV__) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateDOMNesting(null, String(content), this, this._ancestorInfo);
|
||||
this._contentDebugID = contentDebugID;
|
||||
if (hasExistingContent) {
|
||||
ReactInstrumentation.debugTool.onBeforeUpdateComponent(contentDebugID, content);
|
||||
@@ -492,7 +493,7 @@ function ReactDOMComponent(element) {
|
||||
this._flags = 0;
|
||||
if (__DEV__) {
|
||||
this._ancestorInfo = null;
|
||||
setContentChildForInstrumentation.call(this, null);
|
||||
setAndValidateContentChildDev.call(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,7 +599,7 @@ ReactDOMComponent.Mixin = {
|
||||
if (parentInfo) {
|
||||
// parentInfo should always be present except for the top-level
|
||||
// component when server rendering
|
||||
validateDOMNesting(this._tag, this, parentInfo);
|
||||
validateDOMNesting(this._tag, null, this, parentInfo);
|
||||
}
|
||||
this._ancestorInfo =
|
||||
validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
|
||||
@@ -794,7 +795,7 @@ ReactDOMComponent.Mixin = {
|
||||
// TODO: Validate that text is allowed as a child of this node
|
||||
ret = escapeTextContentForBrowser(contentToUse);
|
||||
if (__DEV__) {
|
||||
setContentChildForInstrumentation.call(this, contentToUse);
|
||||
setAndValidateContentChildDev.call(this, contentToUse);
|
||||
}
|
||||
} else if (childrenToUse != null) {
|
||||
var mountImages = this.mountChildren(
|
||||
@@ -836,7 +837,7 @@ ReactDOMComponent.Mixin = {
|
||||
if (contentToUse != null) {
|
||||
// TODO: Validate that text is allowed as a child of this node
|
||||
if (__DEV__) {
|
||||
setContentChildForInstrumentation.call(this, contentToUse);
|
||||
setAndValidateContentChildDev.call(this, contentToUse);
|
||||
}
|
||||
DOMLazyTree.queueText(lazyTree, contentToUse);
|
||||
} else if (childrenToUse != null) {
|
||||
@@ -1110,7 +1111,7 @@ ReactDOMComponent.Mixin = {
|
||||
if (lastContent !== nextContent) {
|
||||
this.updateTextContent('' + nextContent);
|
||||
if (__DEV__) {
|
||||
setContentChildForInstrumentation.call(this, nextContent);
|
||||
setAndValidateContentChildDev.call(this, nextContent);
|
||||
}
|
||||
}
|
||||
} else if (nextHtml != null) {
|
||||
@@ -1122,7 +1123,7 @@ ReactDOMComponent.Mixin = {
|
||||
}
|
||||
} else if (nextChildren != null) {
|
||||
if (__DEV__) {
|
||||
setContentChildForInstrumentation.call(this, null);
|
||||
setAndValidateContentChildDev.call(this, null);
|
||||
}
|
||||
|
||||
this.updateChildren(nextChildren, transaction, context);
|
||||
@@ -1185,7 +1186,7 @@ ReactDOMComponent.Mixin = {
|
||||
this._wrapperState = null;
|
||||
|
||||
if (__DEV__) {
|
||||
setContentChildForInstrumentation.call(this, null);
|
||||
setAndValidateContentChildDev.call(this, null);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ Object.assign(ReactDOMTextComponent.prototype, {
|
||||
if (parentInfo) {
|
||||
// parentInfo should always be present except for the top-level
|
||||
// component when server rendering
|
||||
validateDOMNesting('#text', this, parentInfo);
|
||||
validateDOMNesting(null, this._stringText, this, parentInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -777,7 +777,7 @@ describe('ReactDOMComponent', function() {
|
||||
});
|
||||
|
||||
it('should work error event on <source> element', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOn(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<video>
|
||||
@@ -1224,7 +1224,7 @@ describe('ReactDOMComponent', function() {
|
||||
|
||||
class Row extends React.Component {
|
||||
render() {
|
||||
return <tr />;
|
||||
return <tr>x</tr>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1236,15 +1236,21 @@ describe('ReactDOMComponent', function() {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
|
||||
'<table>. See Foo > table > Row > tr. Add a <tbody> to your code to ' +
|
||||
'match the DOM tree generated by the browser.'
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: validateDOMNesting(...): #text cannot appear as a child ' +
|
||||
'of <table>. See Foo > table > #text.'
|
||||
'Warning: validateDOMNesting(...): Text nodes cannot appear as a ' +
|
||||
'child of <tr>. See Row > tr > #text.'
|
||||
);
|
||||
expect(console.error.calls.argsFor(2)[0]).toBe(
|
||||
'Warning: validateDOMNesting(...): Whitespace text nodes cannot ' +
|
||||
'appear as a child of <table>. Make sure you don\'t have any extra ' +
|
||||
'whitespace between tags on each line of your source code. See Foo > ' +
|
||||
'table > #text.'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user