Skip to content

Commit 7292ee0

Browse files
committed
Support more empty types
Undefined errors as a direct return value. This changes semantics for "true" and functions to mirror the client.
1 parent bdc23c3 commit 7292ee0

File tree

1 file changed

+97
-33
lines changed

1 file changed

+97
-33
lines changed

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
import ReactSharedInternals from 'shared/ReactSharedInternals';
6167
import {
6268
disableLegacyContext,
@@ -421,6 +427,16 @@ function shouldConstruct(Component) {
421427
return Component.prototype && Component.prototype.isReactComponent;
422428
}
423429

430+
function invalidRenderResult(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
function renderWithHooks<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
const children = Component(props, secondArg);
449+
if (children === undefined) {
450+
invalidRenderResult(Component);
451+
}
433452
return children;
434453
}
435454

@@ -441,6 +460,13 @@ function finishClassComponent(
441460
props: any,
442461
): ReactNodeList {
443462
const nextChildren = 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 (typeof node === 'object' && node !== null) {
724+
switch ((node: any).$$typeof) {
725+
case REACT_ELEMENT_TYPE: {
726+
const element: React$Element<any> = (node: any);
727+
const type = element.type;
728+
const props = element.props;
729+
renderElement(request, task, type, props, node);
730+
return;
731+
}
732+
case REACT_PORTAL_TYPE:
733+
throw new Error('Not yet implemented node type.');
734+
case REACT_LAZY_TYPE:
735+
throw new Error('Not yet implemented node type.');
736+
}
737+
738+
if (isArray(node)) {
739+
if (node.length > 0) {
740+
for (let i = 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+
const iteratorFn = getIteratorFn(node);
758+
if (iteratorFn) {
759+
throw new Error('Not yet implemented node type.');
760+
}
761+
762+
const childString = 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 (typeof node === '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 (let i = 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 (typeof node === '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-
typeof node === 'object' &&
733-
node &&
734-
(node: any).$$typeof === REACT_ELEMENT_TYPE
735-
) {
736-
const element: React$Element<any> = (node: any);
737-
const type = element.type;
738-
const props = element.props;
739-
renderElement(request, task, type, props, node);
740-
return;
796+
if (__DEV__) {
797+
if (typeof node === '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-
throw new Error('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
function spawnNewSuspendedTask(

0 commit comments

Comments
 (0)