diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index a42056ab249225..c2074d80418d26 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -207,6 +207,230 @@ describe('ReactDOMFizzServer', () => {
return readText(text);
}
+ // @gate experimental
+ it('should asynchronously load a lazy component', async () => {
+ let resolveA;
+ const LazyA = React.lazy(() => {
+ return new Promise(r => {
+ resolveA = r;
+ });
+ });
+
+ let resolveB;
+ const LazyB = React.lazy(() => {
+ return new Promise(r => {
+ resolveB = r;
+ });
+ });
+
+ function TextWithPunctuation({text, punctuation}) {
+ return ;
+ }
+ // This tests that default props of the inner element is resolved.
+ TextWithPunctuation.defaultProps = {
+ punctuation: '!',
+ };
+
+ await act(async () => {
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
+
+
+ }>
+
+
+
+
+ }>
+
+
+
+
,
+ writable,
+ );
+ startWriting();
+ });
+ expect(getVisibleChildren(container)).toEqual(
+
+
Loading...
+
Loading...
+
,
+ );
+ await act(async () => {
+ resolveA({default: Text});
+ });
+ expect(getVisibleChildren(container)).toEqual(
+ ,
+ );
+ await act(async () => {
+ resolveB({default: TextWithPunctuation});
+ });
+ expect(getVisibleChildren(container)).toEqual(
+ ,
+ );
+ });
+
+ // @gate experimental
+ it('should client render a boundary if a lazy component rejects', async () => {
+ let rejectComponent;
+ const LazyComponent = React.lazy(() => {
+ return new Promise((resolve, reject) => {
+ rejectComponent = reject;
+ });
+ });
+
+ const loggedErrors = [];
+
+ function App({isClient}) {
+ return (
+
+ }>
+ {isClient ? : }
+
+
+ );
+ }
+
+ await act(async () => {
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
+ ,
+ writable,
+ {
+ onError(x) {
+ loggedErrors.push(x);
+ },
+ },
+ );
+ startWriting();
+ });
+ expect(loggedErrors).toEqual([]);
+
+ // Attempt to hydrate the content.
+ const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
+ root.render();
+ Scheduler.unstable_flushAll();
+
+ // We're still loading because we're waiting for the server to stream more content.
+ expect(getVisibleChildren(container)).toEqual(Loading...
);
+
+ expect(loggedErrors).toEqual([]);
+
+ const theError = new Error('Test');
+ await act(async () => {
+ rejectComponent(theError);
+ });
+
+ expect(loggedErrors).toEqual([theError]);
+
+ // We haven't ran the client hydration yet.
+ expect(getVisibleChildren(container)).toEqual(Loading...
);
+
+ // Now we can client render it instead.
+ Scheduler.unstable_flushAll();
+
+ // The client rendered HTML is now in place.
+ expect(getVisibleChildren(container)).toEqual(Hello
);
+
+ expect(loggedErrors).toEqual([theError]);
+ });
+
+ // @gate experimental
+ it('should asynchronously load a lazy element', async () => {
+ let resolveElement;
+ const lazyElement = React.lazy(() => {
+ return new Promise(r => {
+ resolveElement = r;
+ });
+ });
+
+ await act(async () => {
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
+
+ }>
+ {lazyElement}
+
+
,
+ writable,
+ );
+ startWriting();
+ });
+ expect(getVisibleChildren(container)).toEqual(Loading...
);
+ await act(async () => {
+ resolveElement({default: });
+ });
+ expect(getVisibleChildren(container)).toEqual(Hello
);
+ });
+
+ // @gate experimental
+ it('should client render a boundary if a lazy element rejects', async () => {
+ let rejectElement;
+ const element = ;
+ const lazyElement = React.lazy(() => {
+ return new Promise((resolve, reject) => {
+ rejectElement = reject;
+ });
+ });
+
+ const loggedErrors = [];
+
+ function App({isClient}) {
+ return (
+
+ }>
+ {isClient ? element : lazyElement}
+
+
+ );
+ }
+
+ await act(async () => {
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
+ ,
+ writable,
+ {
+ onError(x) {
+ loggedErrors.push(x);
+ },
+ },
+ );
+ startWriting();
+ });
+ expect(loggedErrors).toEqual([]);
+
+ // Attempt to hydrate the content.
+ const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
+ root.render();
+ Scheduler.unstable_flushAll();
+
+ // We're still loading because we're waiting for the server to stream more content.
+ expect(getVisibleChildren(container)).toEqual(Loading...
);
+
+ expect(loggedErrors).toEqual([]);
+
+ const theError = new Error('Test');
+ await act(async () => {
+ rejectElement(theError);
+ });
+
+ expect(loggedErrors).toEqual([theError]);
+
+ // We haven't ran the client hydration yet.
+ expect(getVisibleChildren(container)).toEqual(Loading...
);
+
+ // Now we can client render it instead.
+ Scheduler.unstable_flushAll();
+
+ // The client rendered HTML is now in place.
+ expect(getVisibleChildren(container)).toEqual(Hello
);
+
+ expect(loggedErrors).toEqual([theError]);
+ });
+
// @gate experimental
it('should asynchronously load the suspense boundary', async () => {
await act(async () => {
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index d51ad44ed64e36..f272c18b462db7 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -102,6 +102,7 @@ import {
disableModulePatternComponents,
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
+ enableLazyElements,
} from 'shared/ReactFeatureFlags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
@@ -861,10 +862,15 @@ function renderContextProvider(
function renderLazyComponent(
request: Request,
task: Task,
- type: LazyComponentType,
+ lazyComponent: LazyComponentType,
props: Object,
+ ref: any,
): void {
- throw new Error('Not yet implemented element type.');
+ const payload = lazyComponent._payload;
+ const init = lazyComponent._init;
+ const Component = init(payload);
+ const resolvedProps = resolveDefaultProps(Component, props);
+ return renderElement(request, task, Component, resolvedProps, ref);
}
function renderElement(
@@ -1018,8 +1024,16 @@ function renderNodeDestructive(
'Render them conditionally so that they only appear on the client render.',
);
// eslint-disable-next-line-no-fallthrough
- case REACT_LAZY_TYPE:
- throw new Error('Not yet implemented node type.');
+ case REACT_LAZY_TYPE: {
+ if (enableLazyElements) {
+ const lazyNode: LazyComponentType = (node: any);
+ const payload = lazyNode._payload;
+ const init = lazyNode._init;
+ const resolvedNode = init(payload);
+ renderNodeDestructive(request, task, resolvedNode);
+ return;
+ }
+ }
}
if (isArray(node)) {