Skip to content

Commit b0407b5

Browse files
authored
Support more empty types (#21225)
Undefined errors as a direct return value. This changes semantics for "true" and functions to mirror the client.
1 parent 3971371 commit b0407b5

1 file changed

Lines changed: 97 additions & 33 deletions

File tree

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

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ import {
5656
processChildContext,
5757
emptyContextObject,
5858
}from'./ReactFizzContext';
59-
import{REACT_ELEMENT_TYPE,REACT_SUSPENSE_TYPE}from'shared/ReactSymbols';
59+
import{
60+
getIteratorFn,
61+
REACT_ELEMENT_TYPE,
62+
REACT_PORTAL_TYPE,
63+
REACT_LAZY_TYPE,
64+
REACT_SUSPENSE_TYPE,
65+
}from'shared/ReactSymbols';
6066
importReactSharedInternalsfrom'shared/ReactSharedInternals';
6167
import{
6268
disableLegacyContext,
@@ -421,6 +427,16 @@ function shouldConstruct(Component) {
421427
returnComponent.prototype&&Component.prototype.isReactComponent;
422428
}
423429

430+
functioninvalidRenderResult(type: any): void{
431+
invariant(
432+
false,
433+
'%s(...): Nothing was returned from render. This usually means a '+
434+
'return statement is missing. Or, to render nothing, '+
435+
'return null.',
436+
getComponentNameFromType(type)||'Component',
437+
);
438+
}
439+
424440
functionrenderWithHooks<Props,SecondArg>(
425441
request: Request,
426442
task: Task,
@@ -430,6 +446,9 @@ function renderWithHooks<Props, SecondArg>(
430446
): any{
431447
// TODO: Set up Hooks etc.
432448
constchildren=Component(props,secondArg);
449+
if(children===undefined){
450+
invalidRenderResult(Component);
451+
}
433452
returnchildren;
434453
}
435454

@@ -441,6 +460,13 @@ function finishClassComponent(
441460
props: any,
442461
): ReactNodeList{
443462
constnextChildren=instance.render();
463+
if(nextChildren===undefined){
464+
if(__DEV__&&instance.render._isMockFunction){
465+
// We allow auto-mocks to proceed as if they're returning null.
466+
}else{
467+
invalidRenderResult(Component);
468+
}
469+
}
444470

445471
if(__DEV__){
446472
if(instance.props!==props){
@@ -693,6 +719,58 @@ function renderNodeDestructive(
693719
// something suspends.
694720
task.node=node;
695721

722+
// Handle object types
723+
if(typeofnode==='object'&&node!==null){
724+
switch((node: any).$$typeof){
725+
caseREACT_ELEMENT_TYPE: {
726+
constelement: React$Element<any>=(node: any);
727+
consttype=element.type;
728+
constprops=element.props;
729+
renderElement(request,task,type,props,node);
730+
return;
731+
}
732+
caseREACT_PORTAL_TYPE:
733+
thrownewError('Not yet implemented node type.');
734+
caseREACT_LAZY_TYPE:
735+
thrownewError('Not yet implemented node type.');
736+
}
737+
738+
if(isArray(node)){
739+
if(node.length>0){
740+
for(leti=0;i<node.length;i++){
741+
// Recursively render the rest. We need to use the non-destructive form
742+
// so that we can safely pop back up and render the sibling if something
743+
// suspends.
744+
renderNode(request,task,node[i]);
745+
}
746+
}else{
747+
pushEmpty(
748+
task.blockedSegment.chunks,
749+
request.responseState,
750+
task.assignID,
751+
);
752+
task.assignID=null;
753+
}
754+
return;
755+
}
756+
757+
constiteratorFn=getIteratorFn(node);
758+
if(iteratorFn){
759+
thrownewError('Not yet implemented node type.');
760+
}
761+
762+
constchildString=Object.prototype.toString.call(node);
763+
invariant(
764+
false,
765+
'Objects are not valid as a React child (found: %s). '+
766+
'If you meant to render a collection of children, use an array '+
767+
'instead.',
768+
childString==='[object Object]'
769+
? 'object with keys {'+Object.keys(node).join(', ')+'}'
770+
: childString,
771+
);
772+
}
773+
696774
if(typeofnode==='string'){
697775
pushTextInstance(
698776
task.blockedSegment.chunks,
@@ -704,43 +782,29 @@ function renderNodeDestructive(
704782
return;
705783
}
706784

707-
if(isArray(node)){
708-
if(node.length>0){
709-
for(leti=0;i<node.length;i++){
710-
// Recursively render the rest. We need to use the non-destructive form
711-
// so that we can safely pop back up and render the sibling if something
712-
// suspends.
713-
renderNode(request,task,node[i]);
714-
}
715-
}else{
716-
pushEmpty(
717-
task.blockedSegment.chunks,
718-
request.responseState,
719-
task.assignID,
720-
);
721-
task.assignID=null;
722-
}
723-
return;
724-
}
725-
726-
if(node===null){
727-
pushEmpty(task.blockedSegment.chunks,request.responseState,task.assignID);
785+
if(typeofnode==='number'){
786+
pushTextInstance(
787+
task.blockedSegment.chunks,
788+
''+node,
789+
request.responseState,
790+
task.assignID,
791+
);
792+
task.assignID=null;
728793
return;
729794
}
730795

731-
if(
732-
typeofnode==='object'&&
733-
node&&
734-
(node: any).$$typeof===REACT_ELEMENT_TYPE
735-
){
736-
constelement: React$Element<any>=(node: any);
737-
consttype=element.type;
738-
constprops=element.props;
739-
renderElement(request,task,type,props,node);
740-
return;
796+
if(__DEV__){
797+
if(typeofnode==='function'){
798+
console.error(
799+
'Functions are not valid as a React child. This may happen if '+
800+
'you return a Component instead of <Component /> from render. '+
801+
'Or maybe you meant to call this function rather than return it.',
802+
);
803+
}
741804
}
742805

743-
thrownewError('Not yet implemented node type.');
806+
// Any other type is assumed to be empty.
807+
pushEmpty(task.blockedSegment.chunks,request.responseState,task.assignID);
744808
}
745809

746810
functionspawnNewSuspendedTask(

0 commit comments

Comments
 (0)