Suspense + RSC + API routes - Single query runs 5 times?!
Answered
Barbary Lion posted this in #help-forum
Barbary LionOP
Hey, this is my first big Next.js project and I tried to use Suspense with React Server Component and NextJS API routes.
I don't know what I'm doing wrong, but my RSC loads 5 times even with hardcoded props with 5 call to the API route.
If it was only 2, I'd probably blame the Strict Mode, but 5?
I don't know what I'm doing wrong, but my RSC loads 5 times even with hardcoded props with 5 call to the API route.
If it was only 2, I'd probably blame the Strict Mode, but 5?
Answered by aardani
@Barbary Lion i recommend not putting suspense in a client component
21 Replies
Barbary LionOP
Parent component:
return (
<div className="w-full">
<Suspense fallback={<div className="text-2xl">Loading...</div>}>
<SearchResult query={'Bombtrack'} />
</Suspense>
</div>
);RSC Component:
export default async function SearchResult({ query }: SearchResultProps) {
if (!query) return null;
console.log(query);
const req = await fetch(`api/spotify/searchSong`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ search: query })
});
const data = await req.json();
if (!data) return null;
const results = await data.tracks.items;
return (
<div className="flex flex-col gap-2">
{results.map((i) => (
<SearchResultItem key={i.id} data={i} />
))}
</div>
);
}API route (
app/api/spotify/searchSong/route.ts):export async function POST(request: NextRequest) {
const session = await getServerSession();
if (!session) return new NextResponse('Unauthorized', { status: 401 });
const token = await getSpotifyToken();
const { search } = await request.json();
if (!token) return NextResponse.json({ error: 'Error while getting Spotify token' });
console.log('request');
const spotifyRequest = await fetch(
`https://api.spotify.com/v1/search?q=${encodeURI(search)}&type=track&limit=10`,
{
headers: {
Authorization: `Bearer ${token}`
}
}
);
const data = await spotifyRequest.json();
return NextResponse.json(data);
}Server console:
Browser/Client console:
request
request
request
request
requestBrowser/Client console:
Bombtrack
Bombtrack
Bombtrack
Bombtrack
Bombtrack(I hardcoded the props to
Bombtrack just to elimite possible issues with the props)Barbary LionOP
Barbary LionOP
I tried wrapping the RSC using
{children} method but it still fires multiple times@Barbary Lion can you send full code for Parent component?
@aardani <@327969147671674880> can you send full code for Parent component?
Barbary LionOP
Sorry for late answer
'use client';
import { Suspense, useState } from 'react';
import * as z from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import SearchResult from '@/components/search/SearchResult';
export function SearchSong() {
// const [query, setQuery] = useState<string | null>(null);
const formSchema = z.object({
songname: z.string().min(1, {
message: 'Search term must be at least 1 character long.'
})
});
type Schema = z.infer<typeof formSchema>;
const form = useForm<Schema>({
resolver: zodResolver(formSchema),
defaultValues: {
songname: ''
}
});
const onSubmit = (formData: Schema) => {
// setQuery(formData.songname);
console.log(formData); // disabled for now
};
// part 1// part 2
return (
<div className="w-full">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full flex py-5 gap-5">
<FormField
control={form.control}
name="songname"
render={({ field }) => (
<FormItem className="flex items-center gap-2 flex-grow">
<FormLabel>Spotify</FormLabel>
<FormControl className="">
<Input placeholder="Type your song..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
<Suspense fallback={<div className="text-2xl">Loading...</div>}>
<SearchResult query={'Bombtrack'} />
</Suspense>
</div>
);
}Some leftovers from some tentatives 😅
I could not work on it today, but my next try was going to be: remove completely the React Hook Form, I noticed some strange behaviour (loading appearing when I start to type)
and to be fair it's quite overkill for a simple search bar.. but it was included with the
shadcn/form component that I meant to use originally with a POST to API route (thus the Form seemed like a good option) before going to a RSC (setQuery setting the props for the RSC child)@Barbary Lion i recommend not putting suspense in a client component
Answer
every compeontn thats imported to a "use client" file will be converted/acted as a client component
and client component can't be async (SearchResult)
therefore you either have 2 option
ilft up the. {children} prop to pass servercomponent inside
or
convert SearchResult into client components only
SearchSongor
convert SearchResult into client components only
using async in client components is not yet supported now and will produce unexpected result
@aardani <@327969147671674880> i recommend not putting suspense in a client component
Barbary LionOP
Thanks for the suggestions. It fixed it 💪