Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz] Support more empty types #21225

Merged
merged 1 commit into from
Apr 9, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 97 additions & 33 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ import {
processChildContext,
emptyContextObject,
} from './ReactFizzContext';
import {REACT_ELEMENT_TYPE, REACT_SUSPENSE_TYPE} from 'shared/ReactSymbols';
import {
getIteratorFn,
REACT_ELEMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_LAZY_TYPE,
REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
disableLegacyContext,
Expand Down Expand Up @@ -421,6 +427,16 @@ function shouldConstruct(Component) {
return Component.prototype && Component.prototype.isReactComponent;
}

function invalidRenderResult(type: any): void {
invariant(
false,
'%s(...): Nothing was returned from render. This usually means a ' +
'return statement is missing. Or, to render nothing, ' +
'return null.',
getComponentNameFromType(type) || 'Component',
);
}

function renderWithHooks<Props, SecondArg>(
request: Request,
task: Task,
Expand All @@ -430,6 +446,9 @@ function renderWithHooks<Props, SecondArg>(
): any {
// TODO: Set up Hooks etc.
const children = Component(props, secondArg);
if (children === undefined) {
invalidRenderResult(Component);
}
return children;
}

Expand All @@ -441,6 +460,13 @@ function finishClassComponent(
props: any,
): ReactNodeList {
const nextChildren = instance.render();
if (nextChildren === undefined) {
if (__DEV__ && instance.render._isMockFunction) {
// We allow auto-mocks to proceed as if they're returning null.
} else {
invalidRenderResult(Component);
}
}

if (__DEV__) {
if (instance.props !== props) {
Expand Down Expand Up @@ -693,6 +719,58 @@ function renderNodeDestructive(
// something suspends.
task.node = node;

// Handle object types
if (typeof node === 'object' && node !== null) {
switch ((node: any).$$typeof) {
case REACT_ELEMENT_TYPE: {
const element: React$Element<any> = (node: any);
const type = element.type;
const props = element.props;
renderElement(request, task, type, props, node);
return;
}
case REACT_PORTAL_TYPE:
throw new Error('Not yet implemented node type.');
case REACT_LAZY_TYPE:
throw new Error('Not yet implemented node type.');
}

if (isArray(node)) {
if (node.length > 0) {
for (let i = 0; i < node.length; i++) {
// Recursively render the rest. We need to use the non-destructive form
// so that we can safely pop back up and render the sibling if something
// suspends.
renderNode(request, task, node[i]);
}
} else {
pushEmpty(
task.blockedSegment.chunks,
request.responseState,
task.assignID,
);
task.assignID = null;
}
return;
}

const iteratorFn = getIteratorFn(node);
if (iteratorFn) {
throw new Error('Not yet implemented node type.');
}

const childString = Object.prototype.toString.call(node);
invariant(
false,
'Objects are not valid as a React child (found: %s). ' +
'If you meant to render a collection of children, use an array ' +
'instead.',
childString === '[object Object]'
? 'object with keys {' + Object.keys(node).join(', ') + '}'
: childString,
);
}

if (typeof node === 'string') {
pushTextInstance(
task.blockedSegment.chunks,
Expand All @@ -704,43 +782,29 @@ function renderNodeDestructive(
return;
}

if (isArray(node)) {
if (node.length > 0) {
for (let i = 0; i < node.length; i++) {
// Recursively render the rest. We need to use the non-destructive form
// so that we can safely pop back up and render the sibling if something
// suspends.
renderNode(request, task, node[i]);
}
} else {
pushEmpty(
task.blockedSegment.chunks,
request.responseState,
task.assignID,
);
task.assignID = null;
}
return;
}

if (node === null) {
pushEmpty(task.blockedSegment.chunks, request.responseState, task.assignID);
if (typeof node === 'number') {
pushTextInstance(
task.blockedSegment.chunks,
'' + node,
request.responseState,
task.assignID,
);
task.assignID = null;
return;
}

if (
typeof node === 'object' &&
node &&
(node: any).$$typeof === REACT_ELEMENT_TYPE
) {
const element: React$Element<any> = (node: any);
const type = element.type;
const props = element.props;
renderElement(request, task, type, props, node);
return;
if (__DEV__) {
if (typeof node === 'function') {
console.error(
'Functions are not valid as a React child. This may happen if ' +
'you return a Component instead of <Component /> from render. ' +
'Or maybe you meant to call this function rather than return it.',
);
}
}

throw new Error('Not yet implemented node type.');
// Any other type is assumed to be empty.
pushEmpty(task.blockedSegment.chunks, request.responseState, task.assignID);
}

function spawnNewSuspendedTask(
Expand Down