Next.js Discord

Discord Forum

Problem with server-only annotation when passing server component as props to client component

Answered
Philippine Crocodile posted this in #help-forum
Open in Discord
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
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 following
import '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
View full answer

11 Replies

Philippine CrocodileOP
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
@Philippine Crocodile 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 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 following import '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.
this is not a nextjs bug. you are simply rendering the page as a client component (use client), so you can't import server-only things to there.
make a 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
'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>
   </>
)
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
@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>