Next.js Discord

Discord Forum

Server component requires 'use client' to execute server action

Answered
AM posted this in #help-forum
Open in Discord
Avatar
AMOP
I have following page structure:

import { Label } from '@/components/ui/label';
import React, { Suspense } from 'react';
import ProductList from './ProductList';
import CreateProductForm from './CreateProductForm';

export default async function Products() {
  return (
    <main>
      <Label>Products</Label>
      <Suspense fallback={<Label>Loading Products List...</Label>}>
        <ProductList />
      </Suspense>
      <CreateProductForm />
    </main>
  );
}


And CreateProductForm is like this:

import { createProductAction } from '@/lib/actions/products/actions';

export default function CreateProductForm() {
  return (
    <form
      action={async () => {
        await createProductAction('new product 5');
      }}
    >
      <button type='submit'>Submit</button>
    </form>
  );
}


but i'm facing issue with the createProductAction action

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
  <form action={function} children=...>
Image
Answered by AM
action file:

'use server';

import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';

export async function createProductAction(title: string) {
  const cookieStore = cookies();
  const supabase = createServerActionClient({ cookies: () => cookieStore });
  const {
    data: { session }
  } = await supabase.auth.getSession();
  const user = session?.user;

  if (!user) {
    console.log('User is not authenticated');
    return;
  }

  const res = await supabase.from('todos').insert({ title, user_id: user?.id });

  revalidatePath('/products');
}

export async function fetchProductsAction() {
  const cookieStore = cookies();
  const supabase = createServerActionClient({ cookies: () => cookieStore });
  const {
    data: { session }
  } = await supabase.auth.getSession();
  const user = session?.user;

  const { data: todos, error } = await supabase
    .from('todos')
    .select('*')
    .eq('user_id', user?.id);

  return todos;
}


In the form if I switch from:

action={async () => {
        await createProductAction('new product 5');
}}


to:

action={async () => {
        'use server'
        await createProductAction('new product 5');
}}


works as expected but this shouldn't been required?

Also there is no parent component that is client so i'm not crossing this boundary basically form is direct child of page.tsx it self

What could be the reason for requiring this use server in server component tree?
View full answer

3 Replies

Avatar
AMOP
action file:

'use server';

import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';

export async function createProductAction(title: string) {
  const cookieStore = cookies();
  const supabase = createServerActionClient({ cookies: () => cookieStore });
  const {
    data: { session }
  } = await supabase.auth.getSession();
  const user = session?.user;

  if (!user) {
    console.log('User is not authenticated');
    return;
  }

  const res = await supabase.from('todos').insert({ title, user_id: user?.id });

  revalidatePath('/products');
}

export async function fetchProductsAction() {
  const cookieStore = cookies();
  const supabase = createServerActionClient({ cookies: () => cookieStore });
  const {
    data: { session }
  } = await supabase.auth.getSession();
  const user = session?.user;

  const { data: todos, error } = await supabase
    .from('todos')
    .select('*')
    .eq('user_id', user?.id);

  return todos;
}


In the form if I switch from:

action={async () => {
        await createProductAction('new product 5');
}}


to:

action={async () => {
        'use server'
        await createProductAction('new product 5');
}}


works as expected but this shouldn't been required?

Also there is no parent component that is client so i'm not crossing this boundary basically form is direct child of page.tsx it self

What could be the reason for requiring this use server in server component tree?
Answer
Avatar
I think its because the action props will be serialized and send to client.
try this if you need to pass arg to the server action
import { createProductAction } from '@/lib/actions/products/actions';

export default function CreateProductForm() {
  return (
    <form
      action={createProductAction.bind(null,'new product 5')}
    >
      <button type='submit'>Submit</button>
    </form>
  );
}
Avatar
AMOP
Your suggestion was spot on, and I've already tried it out - it works! I think my understanding was a bit off too. I was under the impression that server components didn't require the 'use server' decoration, while client components did. However, it seems to work just fine in the opposite way. Funny how that turned out! 😄