Next.js Discord

Discord Forum

Suspense with server/client structure

Unanswered
Polish posted this in #help-forum
Open in Discord
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?

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/use
Btw 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 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.