Problem with server-only annotation when passing server component as props to client component
Answered
Philippine Crocodile posted this in #help-forum
Philippine CrocodileOP
hi, I am following this pattern https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props
I have the following setup
client component
page.tsx
However, it fails to compile because the server component has an import
It says that using 'server-only' is not allowed. How do i resolve this without a ton of rewriting?
thanks
full error
I have the following setup
client component
interface ClientComponentNavProps {
genres: Genre[];
selectedLink: string;
onGenreChange: (name: string) => void;
children?: React.ReactNode;
}
export const ClientComponentNav = ({
genres: initialGenres,
selectedLink,
onGenreChange,
children,
}: ClientComponentNavProps) => {
return (
<>
<div>{stuff}</div>
{children}
</>
);
}page.tsx
'use client';
import { useState } from 'react'; // need the use client because useState requires it
import { SliderComponent as ServerComponent } from '@/components/SliderComponent';
import { ClientComponentNav } from '@/components/ClientComponentNav';
const Page = () => {
return (
<>
<ClientComponentNav
genres={queryData}
selectedLink={selectedLink}
onGenreChange={handleGenreChange}
>
<ServerComponent genreName={selectedLink} />
</ClientComponentNav>
</>
)However, it fails to compile because the server component has an import
import { api } from '@/lib/trpc/server'; and api has the followingimport 'server-only';
import { cache } from 'react';
import { headers } from 'next/headers';
...
export const api = ...It says that using 'server-only' is not allowed. How do i resolve this without a ton of rewriting?
thanks
full error
./apps/web/src/lib/trpc/server.ts:1:1
Ecmascript file had an error
> 1 | import 'server-only';
| ^^^^^^^^^^^^^^^^^^^^^
2 |
3 | import { cache } from 'react';
4 | import { headers } from 'next/headers';
You're importing a component that needs server-only. That only works in a Server Component which is not supported in pages/ directory.Answered by joulev
server components cannot have access to client-side react states. you cannot pass states to server components.
you probably want to use search query params for this filtering: https://nextjs-faq.com/sharing-client-side-state-with-server-components
you probably want to use search query params for this filtering: https://nextjs-faq.com/sharing-client-side-state-with-server-components
11 Replies
Philippine CrocodileOP
edit: could be a bug https://github.com/dubinc/dub/issues/258
Philippine CrocodileOP
i think the issue the page getting mounted is a client component itself. i might need to further import it into a server component. but i need to know how to pass state from a client component to its child / server component
Philippine CrocodileOP
i may need to just use server actions
make a
keep this
page.client.tsx or whatever with use client, declare ClientComponentNav in there, handle all the useState and so on in there.keep this
page.tsx a server component.Philippine CrocodileOP
thanks @joulev
i did that but how do I communicate to the child server component that the state changed? do I pass prop to the parent page.tsx and back down?
ClientComponentNav
page.tsx
i did that but how do I communicate to the child server component that the state changed? do I pass prop to the parent page.tsx and back down?
ClientComponentNav
'use client';
import { useState } from 'react';
interface ClientComponentNavProps {
genres: Genre[];
children?: React.ReactNode;
}
export const ClientComponentNav = ({
genres: genres,
children,
}: GenresNavProps) => {
const [selectedGenre, setSelectedGenre] = useState('All');
const handleClick = (name: string) => {
setSelectedGenre(name);
};
return (
<>
{genres.map((genre, idx) => (
<div
key={idx}
onClick={() => handleClick(genre.name as string)}
>
{genre.name}
</div>
{children}
</>
);
};page.tsx
// this is now a server component
import { api } from '@/lib/trpc/server';
const Page = async () => {
const genres = await api.home.getGenreData();
return (
<>
<ClientComponentNav
genres={genres}
>
<ServerComponent genreName={selectedGenre} /> // what do i do here
</ClientComponentNav>
</>
)@Philippine Crocodile thanks <@484037068239142956>
i did that but how do I communicate to the child server component that the state changed? do I pass prop to the parent page.tsx and back down?
ClientComponentNav
'use client';
import { useState } from 'react';
interface ClientComponentNavProps {
genres: Genre[];
children?: React.ReactNode;
}
export const ClientComponentNav = ({
genres: genres,
children,
}: GenresNavProps) => {
const [selectedGenre, setSelectedGenre] = useState('All');
const handleClick = (name: string) => {
setSelectedGenre(name);
};
return (
<>
{genres.map((genre, idx) => (
<div
key={idx}
onClick={() => handleClick(genre.name as string)}
>
{genre.name}
</div>
{children}
</>
);
};
page.tsx
// this is now a server component
import { api } from '@/lib/trpc/server';
const Page = async () => {
const genres = await api.home.getGenreData();
return (
<>
<ClientComponentNav
genres={genres}
>
<ServerComponent genreName={selectedGenre} /> // what do i do here
</ClientComponentNav>
</>
)
server components cannot have access to client-side react states. you cannot pass states to server components.
you probably want to use search query params for this filtering: https://nextjs-faq.com/sharing-client-side-state-with-server-components
you probably want to use search query params for this filtering: https://nextjs-faq.com/sharing-client-side-state-with-server-components
Answer
Philippine CrocodileOP
thanks @joulev it is working now using the query string approach 🩶
Philippine CrocodileOP
however is it really necessary to change the url, pass search params to an RSC to perform a new query & render for a simple filter?
i am going to have other server components on the page
I tested it with different selected values, and the rsc refetching is not the fastest because it is a new query that is not memoized and takes 1-2 seconds
wouldnt there be faster ways that fetch the full dataset once and render a subset on every subsequent onclick?
Im trying to say that this case is not necessarily a onchange search result scenario
i am going to have other server components on the page
I tested it with different selected values, and the rsc refetching is not the fastest because it is a new query that is not memoized and takes 1-2 seconds
wouldnt there be faster ways that fetch the full dataset once and render a subset on every subsequent onclick?
Im trying to say that this case is not necessarily a onchange search result scenario
@Philippine Crocodile however is it really necessary to change the url, pass search params to an RSC to perform a new query & render for a simple filter?
i am going to have other server components on the page
I tested it with different selected values, and the rsc refetching is not the fastest because it is a new query that is not memoized and takes 1-2 seconds
wouldnt there be faster ways that fetch the full dataset once and render a subset on every subsequent onclick?
Im trying to say that this case is not necessarily a onchange search result scenario
however is it really necessary to change the url, pass search params to an RSC to perform a new query & render for a simple filter?yes.
server components are essentially server-side routes that return html. so it can only access things that normal server-side routes can access: url pathname, search query params, cookies.
search query param is the most practical and common method.
wouldnt there be faster ways that fetch the full dataset once and render a subset on every subsequent onclick?if you want to, why not. then just fetch everything in the page, then pass the whole data to a client component which does the filtering.
// this is now a server component
import { api } from '@/lib/trpc/server';
const allData = await api.home.getGenreData();
return <PageClient allData={allData} />"use client";
const [selectedGenre, setSelectedGenre] = useState('All');
const data = allData.filter(...);
<ClientComponentNav genres={genres}>
// render the data here
</ClientComponentNav>