Skip to content

Commit 2626e3e

Browse files
committed
Implement lazy elements / nodes
This is used by Flight to encode not yet resolved nodes of any kind.
1 parent da21350 commit 2626e3e

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,98 @@ describe('ReactDOMFizzServer', () => {
339339
expect(loggedErrors).toEqual([theError]);
340340
});
341341

342+
// @gate experimental
343+
it('should asynchronously load a lazy element', async () => {
344+
let resolveElement;
345+
const lazyElement = React.lazy(() => {
346+
return new Promise(r => {
347+
resolveElement = r;
348+
});
349+
});
350+
351+
await act(async () => {
352+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
353+
<div>
354+
<Suspense fallback={<Text text="Loading..." />}>
355+
{lazyElement}
356+
</Suspense>
357+
</div>,
358+
writable,
359+
);
360+
startWriting();
361+
});
362+
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
363+
await act(async () => {
364+
resolveElement({default: <Text text="Hello" />});
365+
});
366+
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
367+
});
368+
369+
// @gate experimental
370+
it('should client render a boundary if a lazy element rejects', async () => {
371+
let rejectElement;
372+
const element = <Text text="Hello" />;
373+
const lazyElement = React.lazy(() => {
374+
return new Promise((resolve, reject) => {
375+
rejectElement = reject;
376+
});
377+
});
378+
379+
const loggedErrors = [];
380+
381+
function App({isClient}) {
382+
return (
383+
<div>
384+
<Suspense fallback={<Text text="Loading..." />}>
385+
{isClient ? element : lazyElement}
386+
</Suspense>
387+
</div>
388+
);
389+
}
390+
391+
await act(async () => {
392+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
393+
<App isClient={false} />,
394+
writable,
395+
{
396+
onError(x) {
397+
loggedErrors.push(x);
398+
},
399+
},
400+
);
401+
startWriting();
402+
});
403+
expect(loggedErrors).toEqual([]);
404+
405+
// Attempt to hydrate the content.
406+
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
407+
root.render(<App isClient={true} />);
408+
Scheduler.unstable_flushAll();
409+
410+
// We're still loading because we're waiting for the server to stream more content.
411+
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
412+
413+
expect(loggedErrors).toEqual([]);
414+
415+
const theError = new Error('Test');
416+
await act(async () => {
417+
rejectElement(theError);
418+
});
419+
420+
expect(loggedErrors).toEqual([theError]);
421+
422+
// We haven't ran the client hydration yet.
423+
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
424+
425+
// Now we can client render it instead.
426+
Scheduler.unstable_flushAll();
427+
428+
// The client rendered HTML is now in place.
429+
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
430+
431+
expect(loggedErrors).toEqual([theError]);
432+
});
433+
342434
// @gate experimental
343435
it('should asynchronously load the suspense boundary', async () => {
344436
await act(async () => {

packages/react-server/src/ReactFizzServer.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import {
102102
disableModulePatternComponents,
103103
warnAboutDefaultPropsOnFunctionComponents,
104104
enableScopeAPI,
105+
enableLazyElements,
105106
} from 'shared/ReactFeatureFlags';
106107

107108
import getComponentNameFromType from 'shared/getComponentNameFromType';
@@ -1023,8 +1024,16 @@ function renderNodeDestructive(
10231024
'Render them conditionally so that they only appear on the client render.',
10241025
);
10251026
// eslint-disable-next-line-no-fallthrough
1026-
case REACT_LAZY_TYPE:
1027-
throw new Error('Not yet implemented node type.');
1027+
case REACT_LAZY_TYPE: {
1028+
if (enableLazyElements) {
1029+
const lazyNode: LazyComponentType<any, any> = (node: any);
1030+
const payload = lazyNode._payload;
1031+
const init = lazyNode._init;
1032+
const resolvedNode = init(payload);
1033+
renderNodeDestructive(request, task, resolvedNode);
1034+
return;
1035+
}
1036+
}
10281037
}
10291038

10301039
if (isArray(node)) {

0 commit comments

Comments
 (0)