Server component requires 'use client' to execute server action
Answered
AM posted this in #help-forum
AMOP
I have following page structure:
And
but i'm facing issue with the
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
actionError: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
<form action={function} children=...>
Answered by AM
action file:
In the form if I switch from:
to:
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
What could be the reason for requiring this
'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?3 Replies
AMOP
action file:
In the form if I switch from:
to:
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
What could be the reason for requiring this
'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
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
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>
);
}
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! 😄