Suspense isn't catching my Promise
Answered
XtratoS posted this in #help-forum
XtratoSOP
page.tsx
ResultsTable.tsx
actions.ts
Suspense doesn't actually load the page, the page transitions from a state to another directly, so the sekelton is never shown!
export default async function Page({ searchParams }: PageProps) {
return (
<div className="flex flex-col gap-8">
<SearchInput
query={searchParams.q || ''}
/>
<Suspense fallback={<ResultsTableSekelton />}>
<ResultsTable
query={searchParams.q || ''}
/>
</Suspense>
</div>
)
}ResultsTable.tsx
export default async function ResultsTable({ query }: PageProps) {
const searchResults = await searchChannels(query);
const exactMatch = searchResults?.find((result) => result.broadcaster_login.toLowerCase() === query.toLowerCase());
return (
<BoxContainer>
{exactMatch && <>
<Header header="Exact Match" />
<Row {...exactMatch} />
</>}
<Header header="Results" />
{searchResults?.map((result, index) => (
<Row key={index} {...result} />
))}
</BoxContainer>
)
}actions.ts
'use server';
export async function searchChannels(name: string): Promise<TwitchAPI.SearchChannel[]> {
const token = (await getCustomSession() as CustomSession).accessToken;
const result = await fetchSearchChannels({ query: name, token });
return result;
}Suspense doesn't actually load the page, the page transitions from a state to another directly, so the sekelton is never shown!
Answered by Cape lion
Firstly, your "searchChannels" does not need to be a server action. That will create an API route (un-necessarily) for it, since
Other than that, I don't exactly see any reason why your skeleton wouldn't be showing, I haven't played with suspense boundaries too much yet, so there may be a detail that I'm not seeing.
The only thing I can think of is that your data is being cached, or the fetch is fast enough for it not to be noticed 🙂
ResultsTable is a server component, you can just call that function without needing the "use server"; directive.Other than that, I don't exactly see any reason why your skeleton wouldn't be showing, I haven't played with suspense boundaries too much yet, so there may be a detail that I'm not seeing.
The only thing I can think of is that your data is being cached, or the fetch is fast enough for it not to be noticed 🙂
4 Replies
Cape lion
Firstly, your "searchChannels" does not need to be a server action. That will create an API route (un-necessarily) for it, since
Other than that, I don't exactly see any reason why your skeleton wouldn't be showing, I haven't played with suspense boundaries too much yet, so there may be a detail that I'm not seeing.
The only thing I can think of is that your data is being cached, or the fetch is fast enough for it not to be noticed 🙂
ResultsTable is a server component, you can just call that function without needing the "use server"; directive.Other than that, I don't exactly see any reason why your skeleton wouldn't be showing, I haven't played with suspense boundaries too much yet, so there may be a detail that I'm not seeing.
The only thing I can think of is that your data is being cached, or the fetch is fast enough for it not to be noticed 🙂
Answer
@XtratoS page.tsx
ts
export default async function Page({ searchParams }: PageProps) {
return (
<div className="flex flex-col gap-8">
<SearchInput
query={searchParams.q || ''}
/>
<Suspense fallback={<ResultsTableSekelton />}>
<ResultsTable
query={searchParams.q || ''}
/>
</Suspense>
</div>
)
}
ResultsTable.tsx
ts
export default async function ResultsTable({ query }: PageProps) {
const searchResults = await searchChannels(query);
const exactMatch = searchResults?.find((result) => result.broadcaster_login.toLowerCase() === query.toLowerCase());
return (
<BoxContainer>
{exactMatch && <>
<Header header="Exact Match" />
<Row {...exactMatch} />
</>}
<Header header="Results" />
{searchResults?.map((result, index) => (
<Row key={index} {...result} />
))}
</BoxContainer>
)
}
actions.ts
ts
'use server';
export async function searchChannels(name: string): Promise<TwitchAPI.SearchChannel[]> {
const token = (await getCustomSession() as CustomSession).accessToken;
const result = await fetchSearchChannels({ query: name, token });
return result;
}
Suspense doesn't actually load the page, the page transitions from a state to another directly, so the sekelton is never shown!
Turkish Van
You might want to add
In general,
It might look like this:
By specifying
Reference to the docs: https://react.dev/reference/react/Suspense#resetting-suspense-boundaries-on-navigation
key property inside of Your Suspense boundaries.In general,
React will not trigger the Suspense fallback for already visible content.It might look like this:
const query = searchParams?.q || "";
// The rest of the code
<Suspense key={query} fallback={<ResultsTableSekelton />}>
<ResultsTable
query={query}
/>
</Suspense>By specifying
key property, You are implicitly saying React that You want to trigger the Suspense fallback every time Your query gets changed.Reference to the docs: https://react.dev/reference/react/Suspense#resetting-suspense-boundaries-on-navigation
Also, as caoimhe stated above, there is no reason to make
searchChannels a Server Action.XtratoSOP
Thank you both for your answers, that was really helpful ❤️