diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js
index 5fa2a4f9f2e6e..aae31c3494626 100644
--- a/packages/react-dom/src/__tests__/ReactComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactComponent-test.js
@@ -635,8 +635,9 @@ describe('ReactComponent', () => {
});
}).toErrorDev(
'Warning: Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
+ 'you return Foo instead of from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
+ ' {Foo}\n' +
' in Foo (at **)',
);
});
@@ -656,8 +657,9 @@ describe('ReactComponent', () => {
});
}).toErrorDev(
'Warning: Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
+ 'you return Foo instead of from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
+ ' {Foo}\n' +
' in Foo (at **)',
);
});
@@ -678,8 +680,9 @@ describe('ReactComponent', () => {
});
}).toErrorDev(
'Warning: Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
+ 'you return Foo instead of from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
+ ' {Foo}\n' +
' in span (at **)\n' +
' in div (at **)\n' +
' in Foo (at **)',
@@ -730,13 +733,15 @@ describe('ReactComponent', () => {
});
}).toErrorDev([
'Warning: Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
+ 'you return Foo instead of from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
+ '
{Foo}
\n' +
' in div (at **)\n' +
' in Foo (at **)',
'Warning: Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
+ 'you return Foo instead of from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
+ ' {Foo}\n' +
' in span (at **)\n' +
' in div (at **)\n' +
' in Foo (at **)',
diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
index 7967cf84a2672..78e45c76419b3 100644
--- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
@@ -487,8 +487,23 @@ describe('ReactDOMRoot', () => {
});
}).toErrorDev(
'Functions are not valid as a React child. ' +
- 'This may happen if you return a Component instead of from render. ' +
- 'Or maybe you meant to call this function rather than return it.',
+ 'This may happen if you return Component instead of from render. ' +
+ 'Or maybe you meant to call this function rather than return it.\n' +
+ ' root.render(Component)',
+ {withoutStack: true},
+ );
+ });
+
+ it('warns when given a symbol', () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+
+ expect(() => {
+ ReactDOM.flushSync(() => {
+ root.render(Symbol('foo'));
+ });
+ }).toErrorDev(
+ 'Symbols are not valid as a React child.\n' +
+ ' root.render(Symbol(foo))',
{withoutStack: true},
);
});
diff --git a/packages/react-dom/src/__tests__/ReactLegacyMount-test.js b/packages/react-dom/src/__tests__/ReactLegacyMount-test.js
index 78492811428c3..c27d3ba0e0f4a 100644
--- a/packages/react-dom/src/__tests__/ReactLegacyMount-test.js
+++ b/packages/react-dom/src/__tests__/ReactLegacyMount-test.js
@@ -71,8 +71,9 @@ describe('ReactMount', () => {
expect(() => ReactTestUtils.renderIntoDocument(Component)).toErrorDev(
'Functions are not valid as a React child. ' +
- 'This may happen if you return a Component instead of from render. ' +
- 'Or maybe you meant to call this function rather than return it.',
+ 'This may happen if you return Component instead of from render. ' +
+ 'Or maybe you meant to call this function rather than return it.\n' +
+ ' root.render(Component)',
{withoutStack: true},
);
});
diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js
index 6c273faed4b0b..e0feae7b39720 100644
--- a/packages/react-reconciler/src/ReactChildFiber.js
+++ b/packages/react-reconciler/src/ReactChildFiber.js
@@ -33,7 +33,13 @@ import {
REACT_LAZY_TYPE,
REACT_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
-import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
+import {
+ ClassComponent,
+ HostRoot,
+ HostText,
+ HostPortal,
+ Fragment,
+} from './ReactWorkTags';
import isArray from 'shared/isArray';
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
@@ -79,6 +85,7 @@ let didWarnAboutGenerators;
let didWarnAboutStringRefs;
let ownerHasKeyUseWarning;
let ownerHasFunctionTypeWarning;
+let ownerHasSymbolTypeWarning;
let warnForMissingKey = (child: mixed, returnFiber: Fiber) => {};
if (__DEV__) {
@@ -93,6 +100,7 @@ if (__DEV__) {
*/
ownerHasKeyUseWarning = ({}: {[string]: boolean});
ownerHasFunctionTypeWarning = ({}: {[string]: boolean});
+ ownerHasSymbolTypeWarning = ({}: {[string]: boolean});
warnForMissingKey = (child: mixed, returnFiber: Fiber) => {
if (child === null || typeof child !== 'object') {
@@ -267,20 +275,68 @@ function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
);
}
-function warnOnFunctionType(returnFiber: Fiber) {
+function warnOnFunctionType(returnFiber: Fiber, invalidChild: Function) {
if (__DEV__) {
- const componentName = getComponentNameFromFiber(returnFiber) || 'Component';
+ const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
- if (ownerHasFunctionTypeWarning[componentName]) {
+ if (ownerHasFunctionTypeWarning[parentName]) {
return;
}
- ownerHasFunctionTypeWarning[componentName] = true;
+ ownerHasFunctionTypeWarning[parentName] = true;
+
+ const name = invalidChild.displayName || invalidChild.name || 'Component';
+
+ if (returnFiber.tag === HostRoot) {
+ console.error(
+ 'Functions are not valid as a React child. This may happen if ' +
+ 'you return %s instead of <%s /> from render. ' +
+ 'Or maybe you meant to call this function rather than return it.\n' +
+ ' root.render(%s)',
+ name,
+ name,
+ name,
+ );
+ } else {
+ console.error(
+ 'Functions are not valid as a React child. This may happen if ' +
+ 'you return %s instead of <%s /> from render. ' +
+ 'Or maybe you meant to call this function rather than return it.\n' +
+ ' <%s>{%s}%s>',
+ name,
+ name,
+ parentName,
+ name,
+ parentName,
+ );
+ }
+ }
+}
- console.error(
- 'Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
- 'Or maybe you meant to call this function rather than return it.',
- );
+function warnOnSymbolType(returnFiber: Fiber, invalidChild: symbol) {
+ if (__DEV__) {
+ const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
+
+ if (ownerHasSymbolTypeWarning[parentName]) {
+ return;
+ }
+ ownerHasSymbolTypeWarning[parentName] = true;
+
+ // eslint-disable-next-line react-internal/safe-string-coercion
+ const name = String(invalidChild);
+
+ if (returnFiber.tag === HostRoot) {
+ console.error(
+ 'Symbols are not valid as a React child.\n' + ' root.render(%s)',
+ name,
+ );
+ } else {
+ console.error(
+ 'Symbols are not valid as a React child.\n' + ' <%s>%s%s>',
+ parentName,
+ name,
+ parentName,
+ );
+ }
}
}
@@ -656,7 +712,10 @@ function createChildReconciler(
if (__DEV__) {
if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
+ warnOnFunctionType(returnFiber, newChild);
+ }
+ if (typeof newChild === 'symbol') {
+ warnOnSymbolType(returnFiber, newChild);
}
}
@@ -778,7 +837,10 @@ function createChildReconciler(
if (__DEV__) {
if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
+ warnOnFunctionType(returnFiber, newChild);
+ }
+ if (typeof newChild === 'symbol') {
+ warnOnSymbolType(returnFiber, newChild);
}
}
@@ -894,7 +956,10 @@ function createChildReconciler(
if (__DEV__) {
if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
+ warnOnFunctionType(returnFiber, newChild);
+ }
+ if (typeof newChild === 'symbol') {
+ warnOnSymbolType(returnFiber, newChild);
}
}
@@ -1621,7 +1686,10 @@ function createChildReconciler(
if (__DEV__) {
if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
+ warnOnFunctionType(returnFiber, newChild);
+ }
+ if (typeof newChild === 'symbol') {
+ warnOnSymbolType(returnFiber, newChild);
}
}
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index e8ea394386df6..5faefdc7d0da6 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -2137,6 +2137,27 @@ function validateIterable(iterable, iteratorFn: Function): void {
}
}
+function warnOnFunctionType(invalidChild: Function) {
+ if (__DEV__) {
+ const name = invalidChild.displayName || invalidChild.name || 'Component';
+ console.error(
+ 'Functions are not valid as a React child. This may happen if ' +
+ 'you return %s instead of <%s /> from render. ' +
+ 'Or maybe you meant to call this function rather than return it.',
+ name,
+ name,
+ );
+ }
+}
+
+function warnOnSymbolType(invalidChild: symbol) {
+ if (__DEV__) {
+ // eslint-disable-next-line react-internal/safe-string-coercion
+ const name = String(invalidChild);
+ console.error('Symbols are not valid as a React child.\n' + ' %s', name);
+ }
+}
+
// This function by it self renders a node and consumes the task by mutating it
// to update the current execution state.
function renderNodeDestructive(
@@ -2329,11 +2350,10 @@ function renderNodeDestructive(
if (__DEV__) {
if (typeof node === 'function') {
- console.error(
- 'Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
- 'Or maybe you meant to call this function rather than return it.',
- );
+ warnOnFunctionType(node);
+ }
+ if (typeof node === 'symbol') {
+ warnOnSymbolType(node);
}
}
}