@samitouri / QOS-React-2 / commits / b0407b55ff

Support more empty types (#21225)

Undefined errors as a direct return value. This changes semantics for "true" and functions to mirror the client.

Sebastian Markbåge committed Apr 9, 2021 at 19:23 UTC b0407b55ff59c60530c85a1ed14b55950d94cb9a
1 file changed +97 -33
packages/react-server/src/ReactFizzServer.js
+97 -33
@@ -56,7 +56,13 @@ import {
56 processChildContext,
57 emptyContextObject,
58 } 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';
66 import ReactSharedInternals from 'shared/ReactSharedInternals';
67 import {
68 disableLegacyContext,
@@ -421,6 +427,16 @@ function shouldConstruct(Component) {
427 return Component.prototype && Component.prototype.isReactComponent;
428 }
429
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 +
440 function renderWithHooks<Props, SecondArg>(
441 request: Request,
442 task: Task,
@@ -430,6 +446,9 @@ function renderWithHooks<Props, SecondArg>(
446 ): any {
447 // TODO: Set up Hooks etc.
448 const children = Component(props, secondArg);
449 + if (children === undefined) {
450 + invalidRenderResult(Component);
451 + }
452 return children;
453 }
454
@@ -441,6 +460,13 @@ function finishClassComponent(
460 props: any,
461 ): ReactNodeList {
462 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 + }
470
471 if (__DEV__) {
472 if (instance.props !== props) {
@@ -693,6 +719,58 @@ function renderNodeDestructive(
719 // something suspends.
720 task.node = node;
721
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 +
774 if (typeof node === 'string') {
775 pushTextInstance(
776 task.blockedSegment.chunks,
@@ -704,43 +782,29 @@ function renderNodeDestructive(
782 return;
783 }
784
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;
793 return;
794 }
795
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 + }
804 }
805
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);
808 }
809
810 function spawnNewSuspendedTask(