@samitouri / QOS-React / commits / c1fd2a91b1

Include the function name for context on invalid function child (#28362)

Also warn for symbols. It's weird because for objects we throw a hard error but functions we do a dev only check. Mainly because we have an object branch anyway. In the object branch we have some built-ins that have bad errors like forwardRef and memo but since they're going to become functions later, I didn't bother updating those. Once they're functions those names will be part of this.

Sebastian Markbåge committed Feb 17, 2024 at 16:41 UTC c1fd2a91b1042c137d750be85e5998f699a54d2a
5 files changed +137 -28
packages/react-dom/src/__tests__/ReactComponent-test.js
+10 -5
@@ -635,8 +635,9 @@ describe('ReactComponent', () => {
635 });
636 }).toErrorDev(
637 'Warning: Functions are not valid as a React child. This may happen if ' +
638 - 'you return a Component instead of <Component /> from render. ' +
638 + 'you return Foo instead of <Foo /> from render. ' +
639 'Or maybe you meant to call this function rather than return it.\n' +
640 + ' <Foo>{Foo}</Foo>\n' +
641 ' in Foo (at **)',
642 );
643 });
@@ -656,8 +657,9 @@ describe('ReactComponent', () => {
657 });
658 }).toErrorDev(
659 'Warning: Functions are not valid as a React child. This may happen if ' +
659 - 'you return a Component instead of <Component /> from render. ' +
660 + 'you return Foo instead of <Foo /> from render. ' +
661 'Or maybe you meant to call this function rather than return it.\n' +
662 + ' <Foo>{Foo}</Foo>\n' +
663 ' in Foo (at **)',
664 );
665 });
@@ -678,8 +680,9 @@ describe('ReactComponent', () => {
680 });
681 }).toErrorDev(
682 'Warning: Functions are not valid as a React child. This may happen if ' +
681 - 'you return a Component instead of <Component /> from render. ' +
683 + 'you return Foo instead of <Foo /> from render. ' +
684 'Or maybe you meant to call this function rather than return it.\n' +
685 + ' <span>{Foo}</span>\n' +
686 ' in span (at **)\n' +
687 ' in div (at **)\n' +
688 ' in Foo (at **)',
@@ -730,13 +733,15 @@ describe('ReactComponent', () => {
733 });
734 }).toErrorDev([
735 'Warning: Functions are not valid as a React child. This may happen if ' +
733 - 'you return a Component instead of <Component /> from render. ' +
736 + 'you return Foo instead of <Foo /> from render. ' +
737 'Or maybe you meant to call this function rather than return it.\n' +
738 + ' <div>{Foo}</div>\n' +
739 ' in div (at **)\n' +
740 ' in Foo (at **)',
741 'Warning: Functions are not valid as a React child. This may happen if ' +
738 - 'you return a Component instead of <Component /> from render. ' +
742 + 'you return Foo instead of <Foo /> from render. ' +
743 'Or maybe you meant to call this function rather than return it.\n' +
744 + ' <span>{Foo}</span>\n' +
745 ' in span (at **)\n' +
746 ' in div (at **)\n' +
747 ' in Foo (at **)',
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+17 -2
@@ -487,8 +487,23 @@ describe('ReactDOMRoot', () => {
487 });
488 }).toErrorDev(
489 'Functions are not valid as a React child. ' +
490 - 'This may happen if you return a Component instead of <Component /> from render. ' +
491 - 'Or maybe you meant to call this function rather than return it.',
490 + 'This may happen if you return Component instead of <Component /> from render. ' +
491 + 'Or maybe you meant to call this function rather than return it.\n' +
492 + ' root.render(Component)',
493 + {withoutStack: true},
494 + );
495 + });
496 +
497 + it('warns when given a symbol', () => {
498 + const root = ReactDOMClient.createRoot(document.createElement('div'));
499 +
500 + expect(() => {
501 + ReactDOM.flushSync(() => {
502 + root.render(Symbol('foo'));
503 + });
504 + }).toErrorDev(
505 + 'Symbols are not valid as a React child.\n' +
506 + ' root.render(Symbol(foo))',
507 {withoutStack: true},
508 );
509 });
packages/react-dom/src/__tests__/ReactLegacyMount-test.js
+3 -2
@@ -71,8 +71,9 @@ describe('ReactMount', () => {
71
72 expect(() => ReactTestUtils.renderIntoDocument(Component)).toErrorDev(
73 'Functions are not valid as a React child. ' +
74 - 'This may happen if you return a Component instead of <Component /> from render. ' +
75 - 'Or maybe you meant to call this function rather than return it.',
74 + 'This may happen if you return Component instead of <Component /> from render. ' +
75 + 'Or maybe you meant to call this function rather than return it.\n' +
76 + ' root.render(Component)',
77 {withoutStack: true},
78 );
79 });
packages/react-reconciler/src/ReactChildFiber.js
+82 -14
@@ -33,7 +33,13 @@ import {
33 REACT_LAZY_TYPE,
34 REACT_CONTEXT_TYPE,
35 } from 'shared/ReactSymbols';
36 -import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
36 +import {
37 + ClassComponent,
38 + HostRoot,
39 + HostText,
40 + HostPortal,
41 + Fragment,
42 +} from './ReactWorkTags';
43 import isArray from 'shared/isArray';
44 import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
45
@@ -79,6 +85,7 @@ let didWarnAboutGenerators;
85 let didWarnAboutStringRefs;
86 let ownerHasKeyUseWarning;
87 let ownerHasFunctionTypeWarning;
88 +let ownerHasSymbolTypeWarning;
89 let warnForMissingKey = (child: mixed, returnFiber: Fiber) => {};
90
91 if (__DEV__) {
@@ -93,6 +100,7 @@ if (__DEV__) {
100 */
101 ownerHasKeyUseWarning = ({}: {[string]: boolean});
102 ownerHasFunctionTypeWarning = ({}: {[string]: boolean});
103 + ownerHasSymbolTypeWarning = ({}: {[string]: boolean});
104
105 warnForMissingKey = (child: mixed, returnFiber: Fiber) => {
106 if (child === null || typeof child !== 'object') {
@@ -267,20 +275,68 @@ function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
275 );
276 }
277
270 -function warnOnFunctionType(returnFiber: Fiber) {
278 +function warnOnFunctionType(returnFiber: Fiber, invalidChild: Function) {
279 if (__DEV__) {
272 - const componentName = getComponentNameFromFiber(returnFiber) || 'Component';
280 + const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
281
274 - if (ownerHasFunctionTypeWarning[componentName]) {
282 + if (ownerHasFunctionTypeWarning[parentName]) {
283 return;
284 }
277 - ownerHasFunctionTypeWarning[componentName] = true;
285 + ownerHasFunctionTypeWarning[parentName] = true;
286 +
287 + const name = invalidChild.displayName || invalidChild.name || 'Component';
288 +
289 + if (returnFiber.tag === HostRoot) {
290 + console.error(
291 + 'Functions are not valid as a React child. This may happen if ' +
292 + 'you return %s instead of <%s /> from render. ' +
293 + 'Or maybe you meant to call this function rather than return it.\n' +
294 + ' root.render(%s)',
295 + name,
296 + name,
297 + name,
298 + );
299 + } else {
300 + console.error(
301 + 'Functions are not valid as a React child. This may happen if ' +
302 + 'you return %s instead of <%s /> from render. ' +
303 + 'Or maybe you meant to call this function rather than return it.\n' +
304 + ' <%s>{%s}</%s>',
305 + name,
306 + name,
307 + parentName,
308 + name,
309 + parentName,
310 + );
311 + }
312 + }
313 +}
314
279 - console.error(
280 - 'Functions are not valid as a React child. This may happen if ' +
281 - 'you return a Component instead of <Component /> from render. ' +
282 - 'Or maybe you meant to call this function rather than return it.',
283 - );
315 +function warnOnSymbolType(returnFiber: Fiber, invalidChild: symbol) {
316 + if (__DEV__) {
317 + const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
318 +
319 + if (ownerHasSymbolTypeWarning[parentName]) {
320 + return;
321 + }
322 + ownerHasSymbolTypeWarning[parentName] = true;
323 +
324 + // eslint-disable-next-line react-internal/safe-string-coercion
325 + const name = String(invalidChild);
326 +
327 + if (returnFiber.tag === HostRoot) {
328 + console.error(
329 + 'Symbols are not valid as a React child.\n' + ' root.render(%s)',
330 + name,
331 + );
332 + } else {
333 + console.error(
334 + 'Symbols are not valid as a React child.\n' + ' <%s>%s</%s>',
335 + parentName,
336 + name,
337 + parentName,
338 + );
339 + }
340 }
341 }
342
@@ -656,7 +712,10 @@ function createChildReconciler(
712
713 if (__DEV__) {
714 if (typeof newChild === 'function') {
659 - warnOnFunctionType(returnFiber);
715 + warnOnFunctionType(returnFiber, newChild);
716 + }
717 + if (typeof newChild === 'symbol') {
718 + warnOnSymbolType(returnFiber, newChild);
719 }
720 }
721
@@ -778,7 +837,10 @@ function createChildReconciler(
837
838 if (__DEV__) {
839 if (typeof newChild === 'function') {
781 - warnOnFunctionType(returnFiber);
840 + warnOnFunctionType(returnFiber, newChild);
841 + }
842 + if (typeof newChild === 'symbol') {
843 + warnOnSymbolType(returnFiber, newChild);
844 }
845 }
846
@@ -894,7 +956,10 @@ function createChildReconciler(
956
957 if (__DEV__) {
958 if (typeof newChild === 'function') {
897 - warnOnFunctionType(returnFiber);
959 + warnOnFunctionType(returnFiber, newChild);
960 + }
961 + if (typeof newChild === 'symbol') {
962 + warnOnSymbolType(returnFiber, newChild);
963 }
964 }
965
@@ -1621,7 +1686,10 @@ function createChildReconciler(
1686
1687 if (__DEV__) {
1688 if (typeof newChild === 'function') {
1624 - warnOnFunctionType(returnFiber);
1689 + warnOnFunctionType(returnFiber, newChild);
1690 + }
1691 + if (typeof newChild === 'symbol') {
1692 + warnOnSymbolType(returnFiber, newChild);
1693 }
1694 }
1695
packages/react-server/src/ReactFizzServer.js
+25 -5
@@ -2137,6 +2137,27 @@ function validateIterable(iterable, iteratorFn: Function): void {
2137 }
2138 }
2139
2140 +function warnOnFunctionType(invalidChild: Function) {
2141 + if (__DEV__) {
2142 + const name = invalidChild.displayName || invalidChild.name || 'Component';
2143 + console.error(
2144 + 'Functions are not valid as a React child. This may happen if ' +
2145 + 'you return %s instead of <%s /> from render. ' +
2146 + 'Or maybe you meant to call this function rather than return it.',
2147 + name,
2148 + name,
2149 + );
2150 + }
2151 +}
2152 +
2153 +function warnOnSymbolType(invalidChild: symbol) {
2154 + if (__DEV__) {
2155 + // eslint-disable-next-line react-internal/safe-string-coercion
2156 + const name = String(invalidChild);
2157 + console.error('Symbols are not valid as a React child.\n' + ' %s', name);
2158 + }
2159 +}
2160 +
2161 // This function by it self renders a node and consumes the task by mutating it
2162 // to update the current execution state.
2163 function renderNodeDestructive(
@@ -2329,11 +2350,10 @@ function renderNodeDestructive(
2350
2351 if (__DEV__) {
2352 if (typeof node === 'function') {
2332 - console.error(
2333 - 'Functions are not valid as a React child. This may happen if ' +
2334 - 'you return a Component instead of <Component /> from render. ' +
2335 - 'Or maybe you meant to call this function rather than return it.',
2336 - );
2353 + warnOnFunctionType(node);
2354 + }
2355 + if (typeof node === 'symbol') {
2356 + warnOnSymbolType(node);
2357 }
2358 }
2359 }