admin管理员组

文章数量:1188028

I have a client component called CampaignTable. The CampaignTable component expects a columns object to render the columns. Inside my columns object I import a server component called CampaignActions. This is a simple dropdown menu in which I do an api call to get some data and use that data in my popover.

However, because the api call performed in the CampaignActions component, my page becomes very slow and I get an error saying:

Error: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead.

However, to my understanding the component is already a React Server Component. I can solve the issue by removing the async and performing the api call in the top-level page and passing the data down as props such as:

Page > CampaignTable > CampaignActions.

But I wonder why my approach is wrong / causing issues. Because it would be nice if I could just perform the api call in the component that needs it, rather than passing it down.

This is my code

'use client'
import React from "react";

const CampaignTable = (props: CampaignTableProps) => {
const campaignColumns = useMemo(() => (
    [
        columnHelper.accessor("id", {
            header: () => "ID",
            cell: info => info.getValue()
        }),
        columnHelper.display({
            id: "socials",
            header: () => "Actions",
            cell: ({row}) => {
                return (
                    <CampaignActions {...row.original}/>
                )
            }
        }),
    ]
), [])

return (

    <DataTable  
        data={props.campaigns || []}
        columns={campaignColumns}
    />
)}

My CampaignActions code

async function CampaignActions(props: Campaign){
const {data: clients} = await getAllClients().then((res) => res);
return (
    <DataTableActions
        edit={{
            onEdit: async () => {
                await openUpdateCampaignModal({
                    ...props,
                    clients: clients ?? [],
                })
            }
        }}
    />
)}

I have a client component called CampaignTable. The CampaignTable component expects a columns object to render the columns. Inside my columns object I import a server component called CampaignActions. This is a simple dropdown menu in which I do an api call to get some data and use that data in my popover.

However, because the api call performed in the CampaignActions component, my page becomes very slow and I get an error saying:

Error: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead.

However, to my understanding the component is already a React Server Component. I can solve the issue by removing the async and performing the api call in the top-level page and passing the data down as props such as:

Page > CampaignTable > CampaignActions.

But I wonder why my approach is wrong / causing issues. Because it would be nice if I could just perform the api call in the component that needs it, rather than passing it down.

This is my code

'use client'
import React from "react";

const CampaignTable = (props: CampaignTableProps) => {
const campaignColumns = useMemo(() => (
    [
        columnHelper.accessor("id", {
            header: () => "ID",
            cell: info => info.getValue()
        }),
        columnHelper.display({
            id: "socials",
            header: () => "Actions",
            cell: ({row}) => {
                return (
                    <CampaignActions {...row.original}/>
                )
            }
        }),
    ]
), [])

return (

    <DataTable  
        data={props.campaigns || []}
        columns={campaignColumns}
    />
)}

My CampaignActions code

async function CampaignActions(props: Campaign){
const {data: clients} = await getAllClients().then((res) => res);
return (
    <DataTableActions
        edit={{
            onEdit: async () => {
                await openUpdateCampaignModal({
                    ...props,
                    clients: clients ?? [],
                })
            }
        }}
    />
)}
Share Improve this question asked Jul 25, 2023 at 9:52 Re-Angelo Re-Angelo 5372 gold badges8 silver badges23 bronze badges 2
  • By the way, .then((res) => res) does nothing; you can do just const {data: clients} = await getAllClients();. – Sophie Alpert Commented Oct 2, 2023 at 0:25
  • @SophieAlpert Yes that is true, I mistakenly put it there. Also wow, did not expect you to comment. Thanks for all your great work! – Re-Angelo Commented Oct 2, 2023 at 14:35
Add a comment  | 

4 Answers 4

Reset to default 15

I think I figured this out after starting the bounty. The answer is hidden in plain sight.

Error: Server Functions cannot be called during initial render.

Server Functions, not to be confused with Server Components. I'm going to go out on a limb and assume that your

getAllClients()

function is in a file with "use server" at the top, making it a Server Action (or Server Function, as its called in the error message).

THAT is what cannot be called during initial render. Server Actions are intended to be called as part of user interactions. A user clicks a button, which calls a server action.

Server actions are special, and result in a network roundtrip. THAT is why React will not let you call them during initial render.

So have the function which fetches data not be a server action, and this should work for you.

With regards to React Server Components, you can:

  • Import Client Components inside Server Components.
  • Client Components can import "Server Components" as long as they can also run in the browser (they don't use Server-Only features).
  • Client Components cannot import "Server Components" that use Server-Only features. (This is because any Component imported into a Client Component is forced to be treated as a Client Component.)
  • Put Server Component as a child prop to a Client Component inside Server Component.

In your case, your CampaignActions is a server component that tries to get all the clients asynchronicity, presumably a "server only" action. but your CampaignTable is explicitly defined as a client component. Hence you are getting the error:

Error: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead.

Read this (React Server Component RFC): https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#capabilities--constraints-of-server-and-client-components and you will have a better idea how this works.

I faced this same issue and as the error suggests you can not use Server Functions during the initial render, my solution was to use Next Route API instead of server routes:

Before:

export default async function GetBlogList({ recent }: { recent: boolean }) {
  const blogPosts = await fetchBlogPosts({ recent });
  ...
);

After:

export default async function GetBlogList({ recent }: { recent: boolean }) {
  const [loading, setLoading] = useState(false);
  const [blogPosts, setBlogPosts] = useState<any>(null);

  useEffect(() => {
    setLoading(true);

    const fetchBlogPost = cache(async () => {
      const blogPost = await axios.get(`/api/blog`);
      setBlogPosts(blogPost);
      setLoading(false);
    });

    fetchBlogPost();
  }, []);
  ...
}

You just need to await the prefetch call in the RSC (Page) to get around this issue:

In the SRC (Page)

  const bobPromise = queryClient.prefetchQuery({
    queryKey: bobQueryKey,
    queryFn: () => getBob(accessToken),
  } as any);

  const promises = [bobPromise];
  
  await Promise.all(promises);

In the Client

  const { data: bobData } = useSuspenseQuery({
    queryKey: bobQueryKey,
    queryFn: () => getBob(accessToken as any),
  });

Boom, no error

本文标签: