render
throws Promise
for parameterized lazy components #55
Description
I've been experimenting with Preact SSR and Suspense and tried to use preact-ssr-prepass
but found render
would always throw a Promise
. I minified my test case and discovered this has to do with precise usage of lazy
. I have a complete minimal repro here.
Essentially, this repo makes three attempts at defining a lazy component evaluated with Suspense. The first attempt looks like this:
function LazyComponent(): VNode {
return <div>Hello, World!</div>;
}
const Comp = lazy(async () => LazyComponent);
// render as `<Suspense fallback={undefined}><Comp /></Suspense>`.
This attempt renders as you would expect, but also is kind of unnecessary. Let's get a little more complex with attempt 2.
function LazyComponent2(): VNode {
const Comp2 = lazy(async () => {
return () => <span>Hello, World!</span>;
});
return <Comp2 />;
}
// Render as `<Suspense fallback={undefined}><LazyComponent2 /></Suspense>`.
In this attempt we've moved the lazy
call inside the component function to provide a bit more encapsulation. This attempt fails at render time and throws a Promise
object directly with no error message. Not sure exactly what's wrong with this pattern, but clearly putting lazy
inside the function breaks it. Maybe lazy
can't be called at render time?
Let's try attempt 3, which is really just a justification for why you'd want to do this in the first place:
function ParameterizedLazyComponent({ id }: { id: number }): VNode {
const Comp3 = lazy(async () => {
const name = await getNameById(id); // Call an async function with a prop value.
return () => <div>Hello, {name}!</div>;
});
return <Comp3 />;
}
// Does some async work, exactly what is not important here.
async function getNameById(id: number): Promise<string> {
await new Promise<void>((resolve) => {
setTimeout(resolve, 100);
});
return `Name #${id}`;
}
// Render as `<Suspense fallback={undefined}><ParameterizedLazyComponent id={1} /></Suspense>`.
This is the same as attempt 2, except it actually does some meaningful async work. This also fails with the same thrown Promise
. Ultimately this is really what I want to do, invoke an async operation with a parameter which comes from a function prop. The only way I can see of to do this is to move the lazy
call inside the component so it closes over the prop. However this pattern appears to break preact-ssr-prepass
. lazy
doesn't appear to provide any arguments to its callback, so I don't see any other obvious way of getting component prop data into the async operation.
I am new to Preact (and not terribly familiar with the React ecosystem in general) so apologies if this has a well-known answer. This feels like a rendering bug given that it is throwing a Promise
without any error message. If there's a different pattern for developing parameterized lazy components, please let me know. As it stands, I don't see an obvious workaround here which does what I need it to do.