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}', + 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', + 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); } } }