Skip to content

Commit

Permalink
add lazy initialization to resolveModelToJson
Browse files Browse the repository at this point in the history
adding lazying initialization toResolveModelToJson means we use attemptResolveElement's full logic on whatever the resolved type ends up being. This better aligns handling of misued Lazy types like a lazy element being used as a Component or a lazy Component being used as an element.
  • Loading branch information
gnoff committed Mar 10, 2022
1 parent 6b9c8c6 commit a4329f4
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 6 deletions.
74 changes: 74 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,43 @@ describe('ReactFlight', () => {
);
});

it('errors on a Lazy element being used in Component position', async () => {
function SharedComponent({text}) {
return (
<div>
shared<span>{text}</span>
</div>
);
}

let load = null;

const LazyElementDisguisedAsComponent = React.lazy(() => {
return new Promise(res => {
load = () => res({default: <SharedComponent text={'a'} />});
});
});

function ServerComponent() {
return (
<React.Suspense fallback={'Loading...'}>
<LazyElementDisguisedAsComponent text={'b'} />
</React.Suspense>
);
}

const transport = ReactNoopFlightServer.render(<ServerComponent />);

act(() => {
const rootModel = ReactNoopFlightClient.read(transport);
ReactNoop.render(rootModel);
});
expect(ReactNoop).toMatchRenderedOutput('Loading...');
spyOnDevAndProd(console, 'error');
await load();
expect(console.error).toHaveBeenCalledTimes(1);
});

it('can render a lazy element', async () => {
function SharedComponent({text}) {
return (
Expand Down Expand Up @@ -229,6 +266,43 @@ describe('ReactFlight', () => {
);
});

it('errors with lazy value in element position that resolves to Component', async () => {
function SharedComponent({text}) {
return (
<div>
shared<span>{text}</span>
</div>
);
}

let load = null;

const componentDisguisedAsElement = React.lazy(() => {
return new Promise(res => {
load = () => res({default: SharedComponent});
});
});

function ServerComponent() {
return (
<React.Suspense fallback={'Loading...'}>
{componentDisguisedAsElement}
</React.Suspense>
);
}

const transport = ReactNoopFlightServer.render(<ServerComponent />);

act(() => {
const rootModel = ReactNoopFlightClient.read(transport);
ReactNoop.render(rootModel);
});
expect(ReactNoop).toMatchRenderedOutput('Loading...');
spyOnDevAndProd(console, 'error');
await load();
expect(console.error).toHaveBeenCalledTimes(1);
});

it('can render a lazy module reference', async () => {
function ClientComponent() {
return <div>I am client</div>;
Expand Down
9 changes: 3 additions & 6 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,6 @@ function attemptResolveElement(
const render = type.render;
return render(props, undefined);
}
case REACT_ELEMENT_TYPE: {
// this can happen when a lazy component resolves to an element instead of
// a Component.
return attemptResolveElement(type.type, type.key, type.ref, type.props);
}
case REACT_MEMO_TYPE: {
return attemptResolveElement(type.type, key, ref, props);
}
Expand Down Expand Up @@ -508,7 +503,9 @@ export function resolveModelToJSON(
break;
}
case REACT_LAZY_TYPE: {
value = attemptResolveElement(value, null, null, {});
const payload = (value: any)._payload;
const init = (value: any)._init;
value = init(payload);
break;
}
}
Expand Down

0 comments on commit a4329f4

Please sign in to comment.