Suspense with server/client structure
Unanswered
Polish posted this in #help-forum
PolishOP
Hey, I was just wondering if I'm missing something or doing something wrong, beacuse this seems intuative.
I have a client component, where i'm passing down some data from a server component. But I would like to have loading indicators be shown, when the data is being fetched. The only way I can think of, is by creating a new server component file, and wrap my other server component with suspense.
But that's in total 3 files now.
Is there a better way of doing something similar to this?
I have a client component, where i'm passing down some data from a server component. But I would like to have loading indicators be shown, when the data is being fetched. The only way I can think of, is by creating a new server component file, and wrap my other server component with suspense.
But that's in total 3 files now.
Is there a better way of doing something similar to this?
21 Replies
Asian black bear
Using
loading.tsx
or manual Suspense boundaries.PolishOP
Well loading.tsx is not really an option in my case, as it's some reusable components. And yes, it works with normal supsense boundaries, but I was just wondering, if it could be done more simple, as it requires 3 files.
Asian black bear
Not sure why you'd require three files in the most naive way if you don't make it too granular.
async function Server() {
const data = fetch('...')
return <Suspense><Client data={data} /></Suspense>
}
function Client(props) {
const data = use(props.data)
return <div>{data.unicorns}</div>
}
PolishOP
Hm - interesting. Never thought of passing down the promise. What does use do in this scenario, never really looked into that api.
Just tried the same thing without
use
and it seems to work properly.Asian black bear
use
is just the client-side pendant to await
triggering suspense boundaries.Not awaiting promises in the server unblocks requests and allows to stream that way.
PolishOP
So it will just trigger the suspense boundary, if the promise is not done? Or am I misunderstanding it?
Asian black bear
Yeah, the server component doesn't block and the client will render the fallback until the server streams in the resolved promise.
PolishOP
Why does it work then, without use in my scenario?
// server
export default async function page() {
const data = HelloWorld();
return (
<Suspense fallback={<div>Loading...</div>}>
<PageClient data={data} />
</Suspense>
);
}
async function HelloWorld() {
const data = await new Promise<string>((resolve) => {
setTimeout(() => {
resolve("Hello, World!");
}, 1000);
});
return data;
}
//client
import { FC } from "react";
interface pageClientProps {
data: Promise<string>;
}
const PageClient: FC<pageClientProps> = (props) => {
const data = props.data;
return <div>{data}</div>;
};
export default PageClient;
It displays loading, until the promise is done
Asian black bear
I couldn't possibly tell, I haven't been veering off of the happy path to know the exact details.
PolishOP
Don't really know what that means. Maybe nextjs does something automatic?
Asian black bear
I can imagine it's just a coincidence that it works correctly for you, but I couldn't tell exactly.
More info on
use
in general is over here: https://react.dev/reference/react/useBtw absolutely unrelated to the issue here's a nice trick to simplify writing those test promises:
// Instead of doing this in server-side code
await new Promise((resolve) => setTimeout(resolve, 1000))
// Try doing this
import { setTimeout } from 'node:timers/promises'
await setTimeout(1000)
@Asian black bear I can imagine it's just a coincidence that it works correctly for you, but I couldn't tell exactly.
Asian black bear
I don't have the capacity to test it myself and figure out the reasons. Maybe somebody else can chime in on that.
But I hope the idea of using raw promises and
use
-ing them helps with your original question.@Asian black bear Btw absolutely unrelated to the issue here's a nice trick to simplify writing those test promises:
ts
// Instead of doing this in server-side code
await new Promise((resolve) => setTimeout(resolve, 1000))
// Try doing this
import { setTimeout } from 'node:timers/promises'
await setTimeout(1000)
PolishOP
Oh cool - normally i just get copilot to write the normal promise setTimout as it's tedious. Will definitely try that.
@Asian black bear But I hope the idea of using raw promises and `use`-ing them helps with your original question.
PolishOP
Yes, that was definitely what I was looking for. Will just for safety use
React.use
, even though it worked in my scenario. Would still love to know why it does work - seems like it shouldn't by what you were saying.