Skip to content

Commit c1fd2a9

Browse files
authoredFeb 17, 2024
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.
1 parent fef30c2 commit c1fd2a9

File tree

5 files changed

+137
-28
lines changed

5 files changed

+137
-28
lines changed
 

‎packages/react-dom/src/__tests__/ReactComponent-test.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,9 @@ describe('ReactComponent', () => {
635635
});
636636
}).toErrorDev(
637637
'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. ' +
639639
'Or maybe you meant to call this function rather than return it.\n' +
640+
' <Foo>{Foo}</Foo>\n' +
640641
' in Foo (at **)',
641642
);
642643
});
@@ -656,8 +657,9 @@ describe('ReactComponent', () => {
656657
});
657658
}).toErrorDev(
658659
'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. ' +
660661
'Or maybe you meant to call this function rather than return it.\n' +
662+
' <Foo>{Foo}</Foo>\n' +
661663
' in Foo (at **)',
662664
);
663665
});
@@ -678,8 +680,9 @@ describe('ReactComponent', () => {
678680
});
679681
}).toErrorDev(
680682
'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. ' +
682684
'Or maybe you meant to call this function rather than return it.\n' +
685+
' <span>{Foo}</span>\n' +
683686
' in span (at **)\n' +
684687
' in div (at **)\n' +
685688
' in Foo (at **)',
@@ -730,13 +733,15 @@ describe('ReactComponent', () => {
730733
});
731734
}).toErrorDev([
732735
'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. ' +
734737
'Or maybe you meant to call this function rather than return it.\n' +
738+
' <div>{Foo}</div>\n' +
735739
' in div (at **)\n' +
736740
' in Foo (at **)',
737741
'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. ' +
739743
'Or maybe you meant to call this function rather than return it.\n' +
744+
' <span>{Foo}</span>\n' +
740745
' in span (at **)\n' +
741746
' in div (at **)\n' +
742747
' in Foo (at **)',

‎packages/react-dom/src/__tests__/ReactDOMRoot-test.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,23 @@ describe('ReactDOMRoot', () => {
487487
});
488488
}).toErrorDev(
489489
'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))',
492507
{withoutStack: true},
493508
);
494509
});

‎packages/react-dom/src/__tests__/ReactLegacyMount-test.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ describe('ReactMount', () => {
7171

7272
expect(() => ReactTestUtils.renderIntoDocument(Component)).toErrorDev(
7373
'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)',
7677
{withoutStack: true},
7778
);
7879
});

‎packages/react-reconciler/src/ReactChildFiber.js

+82-14
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ import {
3333
REACT_LAZY_TYPE,
3434
REACT_CONTEXT_TYPE,
3535
} 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';
3743
import isArray from 'shared/isArray';
3844
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
3945

@@ -79,6 +85,7 @@ let didWarnAboutGenerators;
7985
let didWarnAboutStringRefs;
8086
let ownerHasKeyUseWarning;
8187
let ownerHasFunctionTypeWarning;
88+
let ownerHasSymbolTypeWarning;
8289
let warnForMissingKey = (child: mixed, returnFiber: Fiber) => {};
8390

8491
if (__DEV__) {
@@ -93,6 +100,7 @@ if (__DEV__) {
93100
*/
94101
ownerHasKeyUseWarning = ({}: {[string]: boolean});
95102
ownerHasFunctionTypeWarning = ({}: {[string]: boolean});
103+
ownerHasSymbolTypeWarning = ({}: {[string]: boolean});
96104

97105
warnForMissingKey = (child: mixed, returnFiber: Fiber) => {
98106
if (child === null || typeof child !== 'object') {
@@ -267,20 +275,68 @@ function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
267275
);
268276
}
269277

270-
function warnOnFunctionType(returnFiber: Fiber) {
278+
function warnOnFunctionType(returnFiber: Fiber, invalidChild: Function) {
271279
if (__DEV__) {
272-
const componentName = getComponentNameFromFiber(returnFiber) || 'Component';
280+
const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
273281

274-
if (ownerHasFunctionTypeWarning[componentName]) {
282+
if (ownerHasFunctionTypeWarning[parentName]) {
275283
return;
276284
}
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+
}
278314

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+
}
284340
}
285341
}
286342

@@ -656,7 +712,10 @@ function createChildReconciler(
656712

657713
if (__DEV__) {
658714
if (typeof newChild === 'function') {
659-
warnOnFunctionType(returnFiber);
715+
warnOnFunctionType(returnFiber, newChild);
716+
}
717+
if (typeof newChild === 'symbol') {
718+
warnOnSymbolType(returnFiber, newChild);
660719
}
661720
}
662721

@@ -778,7 +837,10 @@ function createChildReconciler(
778837

779838
if (__DEV__) {
780839
if (typeof newChild === 'function') {
781-
warnOnFunctionType(returnFiber);
840+
warnOnFunctionType(returnFiber, newChild);
841+
}
842+
if (typeof newChild === 'symbol') {
843+
warnOnSymbolType(returnFiber, newChild);
782844
}
783845
}
784846

@@ -894,7 +956,10 @@ function createChildReconciler(
894956

895957
if (__DEV__) {
896958
if (typeof newChild === 'function') {
897-
warnOnFunctionType(returnFiber);
959+
warnOnFunctionType(returnFiber, newChild);
960+
}
961+
if (typeof newChild === 'symbol') {
962+
warnOnSymbolType(returnFiber, newChild);
898963
}
899964
}
900965

@@ -1621,7 +1686,10 @@ function createChildReconciler(
16211686

16221687
if (__DEV__) {
16231688
if (typeof newChild === 'function') {
1624-
warnOnFunctionType(returnFiber);
1689+
warnOnFunctionType(returnFiber, newChild);
1690+
}
1691+
if (typeof newChild === 'symbol') {
1692+
warnOnSymbolType(returnFiber, newChild);
16251693
}
16261694
}
16271695

‎packages/react-server/src/ReactFizzServer.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -2137,6 +2137,27 @@ function validateIterable(iterable, iteratorFn: Function): void {
21372137
}
21382138
}
21392139

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+
21402161
// This function by it self renders a node and consumes the task by mutating it
21412162
// to update the current execution state.
21422163
function renderNodeDestructive(
@@ -2329,11 +2350,10 @@ function renderNodeDestructive(
23292350

23302351
if (__DEV__) {
23312352
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);
23372357
}
23382358
}
23392359
}

0 commit comments

Comments
 (0)
Please sign in to comment.