Suspense and client components
Unanswered
PepeW posted this in #help-forum
PepeWOP
I'm working on a product page. The logic is the following:
Here's the setup.
Now my question is regarding rendering and suspense.
According to me the rendering should happen this way:
1. the
2. then, the
3. after that the computation is done,
But in fact this is not happening this way.
The
// page.ts
export default async function ProductPage({ params }: { params: { productSlug: string } }) {
// api call to get the product
return <ProductWrapper product={product} />
)
// loading.ts
export default async function Loading() {
return <Skeleton />
)
// ProductWrapper.tsx
"use client"
export function ProductWrapper({ product }: ProductWrapperProps) {
return (
<div>
{/* some components */}
<Suspense fallback={<p>loading</p>}>
<ProductVariantSelector
product={product}
/>
</Suspense>
</div>
)
)
// ProductVariantSelector.tsx
"use client"
export function ProductVariantSelector({ product }: ProductVariantSelectorProps) {
// a lot of computations to transform the product variants into a list of options
// the computation in not async (just some map(), filters(), reduce(), etc.)
return (
<ProductOptions options={options} />
)
)
Here's the setup.
Now my question is regarding rendering and suspense.
According to me the rendering should happen this way:
1. the
loading.ts
is displayed while the product api call is being made2. then, the
ProductWrapper.tsx
is rendered execpt the ProductVariantSelector.tsx
(instead the fallback is displayed)3. after that the computation is done,
ProductVariantSelector.tsx
is rendered.But in fact this is not happening this way.
The
loading.ts
is working fine however the Suspense
is not (the fallback is not displayed)67 Replies
PepeWOP
up
where is the product api being called?
PepeWOP
that's a graphql request to my backend
yes but at which component
PepeWOP
ProductPage
in page.ts
(there is a comment)oh my bad
PepeWOP
np
where is the loading.ts located?
at same level of page.tsx?
PepeWOP
right next to the page
The fall back is not displayed because the one thats doing the async operation is
ProductPage
not ProductVariantSelector
if you want the suspense to work, there has to be async operation being awaited in side any component inside <Suspense>
Generally yes. I havent fully explored any other unique use cases though
but thats how it works
PepeWOP
And it could be a server or client component, if there is a async operation the <Suspense /> will work right ?
well, client component can't be async
so it has to be a server component
PepeWOP
Let's say I have a useQuery() inside my cleint component
It would not work because the component itselft as to be async ?
useQuery is a client-side hook
client-side can't be async
therefore you can use useQuery in client component
its already irrelevant to Suspense because we're talking about something that is already in the scope of Client Component
PepeWOP
Ok I understand
So in my case I want to render my
ProductWrapper
without waiting for the ProductVariantSelector
to do its computation so how coud I do that ?You may use Suspense with Client Component but afaik it has nothing to do with useQuery
unless you enable
suspense
in useQueryIts just that the use of Suspense in client component is a bit uncommon
its outside of the scope of what im able to explain to you
// page.tsx
export default function ProductPage({ params }: { params: { productSlug: string } }) {
return <div>
{/* some CLIENT components */}
<Suspense fallback={<p>loading</p>}>
<ProductVariantSelector/>
</Suspense>
</div>
)
// still on the same page.tsx
function ProductVariantSelector(){
// Api CALL to get the product
return (
<ProductVariantSelectorClient product={product}>
)
}
PepeWOP
Ok I see what you did but my question is not about the product. In fact I need it inside
In my question I added a comment inside
This computation can take up to 8sec (in the worst scenarios) so I would like not to wait for this component to render the page.
My initial solution was to use the
So my question is: Is there a way to differ the render of this component so the rest of the page do not wait for it ?
ProductWrapper
.In my question I added a comment inside
ProductVariantSelector
saying that it's doing a lot of computations (map(), filter(), reduce(), etc).This computation can take up to 8sec (in the worst scenarios) so I would like not to wait for this component to render the page.
My initial solution was to use the
Suspense
but like you explained it doesn't work for client components.So my question is: Is there a way to differ the render of this component so the rest of the page do not wait for it ?
do you want Product Page to render as soon as fetch finishes happen or before fetch finished responding?
PepeWOP
I want to keep the loading.ts and the api call inside the page, that behaviour is working as I want
well, afaik you dont need <suspense> then
just conditionally render in the client side using
useQuery
:DPepeWOP
However I want my ProductWrapper to render without wait for ProductVariantSelector
I know that Suspense is useless now
its not that fully useless, you can use it but its still fairly new
i recommend doing whats written in the blog since its more intuitive :D
PepeWOP
There is a mismatch between what I'm trying to achieve and what you're explaining ^^
I'll try to be as clear as possible.
I'm not using any
The product api call is looking something like this:
My problem is not about this api call and the loading.ts. This part is working as expected.
My question is about the render of
This component is doing a lot of computation (map(), filter, reduce(), etc) that can take up to 8sec.
So I don't want to wait for this component when rendering
My original solution was doing:
But as you explained, doing a
So my question is: How can I render
I'll try to be as clear as possible.
I'm not using any
useQuery()
.The product api call is looking something like this:
const product = await productGraphqlRequest(productSlug)
.My problem is not about this api call and the loading.ts. This part is working as expected.
My question is about the render of
ProductVariantSelector
.This component is doing a lot of computation (map(), filter, reduce(), etc) that can take up to 8sec.
So I don't want to wait for this component when rendering
ProductWrapper
.My original solution was doing:
javascript
// ProductWrapper.tsx
"use client"
export function ProductWrapper({ product }: ProductWrapperProps) {
return (
<div>
{/* some components */}
<Suspense fallback={<p>loading</p>}>
<ProductVariantSelector
product={product}
/>
</Suspense>
</div>
)
)
But as you explained, doing a
<Suspense />
in this scenario is useless.So my question is: How can I render
ProductWrapper
and all its components without waiting for ProductVariantSelector
(and it's computation) ?depends on where you want the computation to happen
do you want the computation to happen in the server or in the client?
PepeWOP
I want my computation to stay inside ProductVariantSelector
I've been reading a lot of the doc while speaking.
Is next/dynamic a good solution ?
Doing something like this:
Is next/dynamic a good solution ?
Doing something like this:
const ProductVariantSelector = dynamic(
() =>
import("../features/product/ProductVariantSelector").then((mod) => mod.ProductVariantSelector),
{
loading: () => <p>Loading...</p>,
ssr: false,
}
)
the solution isnt limited to react query. However, you can implement what react query do with raw js. use query just makes your live easier without needing to worry about recomputing at component re-mount
const [finished, setFinished] = useState(false)
const [data, setData] = useState(null)
useEffect(()=>{
async function compute(){
const data = await expensiveComputationData(params.product)
setData(data)
setFinished(true)
}
compute()
},[])
if(!finished) return <>Loading... </>
else{
return <>{data}</>
}
<Suspense> would probably make the code tidier but i havent tried that approach.
and i dont think its the right use case for dynamic()
and i dont think its the right use case for dynamic()
PepeWOP
Hmmm ok
You did this: const data = await expensiveComputationData(params.product)
But the computation is not async
But the computation is not async
Just a bunch of filter(), map(), reduce(), etc
to transform the data
then make promise :D
PepeWOP
Ok I'll try
And out of curiosity why do you think that dynamic import is not the right use case ?
Because after trying it works as expected
I can the see page except
ProductVariantSelector
(only its fallback) and after some time it rendersits for lazy loading. are you sure the computation are still done in the browser?
PepeWOP
Yes because it's working
PepeWOP
I've remove the Suspense around ProductVariantSelector
oh i meant the
loading
attribute, my bad