Server Actions behaviour
Answered
Turkish Van posted this in #help-forum
Turkish VanOP
I have encountered some interesting behaviour of the Server Actions when trying to submit a form by using them.
The most simple example would be:
In the above example we are using Server Action
But what if I tried to wrap the Server Action with the a function on Server component like this:
it gives the following error:
Than I thought, well that is just the way it works, right? But then I tried making it a Client component (just for the educational purposes):
And guess what? It worked without any error.
Why does it behave the way it does in the given example? What is that thing that am I missing out?
The most simple example would be:
import {signOut} from "@/app/actions";
const Page = () => {
return (
<form action={signOut}>
<button type={'submit'}>Sign Out</button>
</form>
);
};
export default Page;In the above example we are using Server Action
signOut from the Server component and it works.But what if I tried to wrap the Server Action with the a function on Server component like this:
import {signOut} from "@/app/actions";
const Page = () => {
return (
<form action={()=>signOut()}>
<button type={'submit'}>Sign Out</button>
</form>
);
};
export default Page;it gives the following error:
⨯ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". Or maybe you meant to call this function rather than return it.Than I thought, well that is just the way it works, right? But then I tried making it a Client component (just for the educational purposes):
"use client"
import {signOut} from "@/app/actions";
const Page = () => {
return (
<form action={()=>signOut()}>
<button type={'submit'}>Sign Out</button>
</form>
);
};
export default Page;And guess what? It worked without any error.
Why does it behave the way it does in the given example? What is that thing that am I missing out?
Answered by joulev
In a server component, you cannot pass callbacks, but you can pass server actions. So
() => signOut(): no
signOut: yes
async () => {
"use server";
await signOut();
}: yes (this is now a new server action).
In client components you don’t face this restriction.
If you pass a server action directly (not a callback), it will work with js disabled (progressive enhancement). If you pass a callback like () => signOut(), it will (as stated above) only work in client components and will not work with js disabled.
() => signOut(): no
signOut: yes
async () => {
"use server";
await signOut();
}: yes (this is now a new server action).
In client components you don’t face this restriction.
If you pass a server action directly (not a callback), it will work with js disabled (progressive enhancement). If you pass a callback like () => signOut(), it will (as stated above) only work in client components and will not work with js disabled.
8 Replies
Is the sign out action have the direct 'use server' at the top?
@Jboncz Is the sign out action have the direct 'use server' at the top?
Turkish VanOP
Yes.
@Turkish Van I have encountered some interesting behaviour of the Server Actions when trying to submit a form by using them.
The most simple example would be:
ts
import {signOut} from "@/app/actions";
const Page = () => {
return (
<form action={signOut}>
<button type={'submit'}>Sign Out</button>
</form>
);
};
export default Page;
In the above example we are using Server Action `signOut` from the Server component and it works.
But what if I tried to wrap the Server Action with the a function on Server component like this:
ts
import {signOut} from "@/app/actions";
const Page = () => {
return (
<form action={()=>signOut()}>
<button type={'submit'}>Sign Out</button>
</form>
);
};
export default Page;
it gives the following error:
⨯ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". Or maybe you meant to call this function rather than return it.
Than I thought, well that is just the way it works, right? But then I tried making it a Client component (just for the educational purposes):
ts
"use client"
import {signOut} from "@/app/actions";
const Page = () => {
return (
<form action={()=>signOut()}>
<button type={'submit'}>Sign Out</button>
</form>
);
};
export default Page;
And guess what? It worked without any error.
Why does it behave the way it does in the given example? What is that thing that am I missing out?
In a server component, you cannot pass callbacks, but you can pass server actions. So
() => signOut(): no
signOut: yes
async () => {
"use server";
await signOut();
}: yes (this is now a new server action).
In client components you don’t face this restriction.
If you pass a server action directly (not a callback), it will work with js disabled (progressive enhancement). If you pass a callback like () => signOut(), it will (as stated above) only work in client components and will not work with js disabled.
() => signOut(): no
signOut: yes
async () => {
"use server";
await signOut();
}: yes (this is now a new server action).
In client components you don’t face this restriction.
If you pass a server action directly (not a callback), it will work with js disabled (progressive enhancement). If you pass a callback like () => signOut(), it will (as stated above) only work in client components and will not work with js disabled.
Answer
Turkish VanOP
Oh, I get it! Thank You @joulev!
Can I get the documentation reference so I can look up a bit deeper?
Also, is there a way to make it work on Server Component in a way like that? Let's say I wanted to display a toast once the user submits a form. The following code is an example from the Client Component:
Can I get the documentation reference so I can look up a bit deeper?
Also, is there a way to make it work on Server Component in a way like that? Let's say I wanted to display a toast once the user submits a form. The following code is an example from the Client Component:
<form
action={() => {
const promise = signOut();
toast.promise(promise, {
loading: "Signing out...",
success: "You successfully signed out",
error: "Failed to sign out",
});
}}>
{/*...*/}@Turkish Van Oh, I get it! Thank You <@484037068239142956>!
Can I get the documentation reference so I can look up a bit deeper?
Also, is there a way to make it work on Server Component in a way like that? Let's say I wanted to display a toast once the user submits a form. The following code is an example from the Client Component:
ts
<form
action={() => {
const promise = signOut();
toast.promise(promise, {
loading: "Signing out...",
success: "You successfully signed out",
error: "Failed to sign out",
});
}}>
{/*...*/}
toast is a client-only utility so you can't. you need client components here. if you don't want to use client actions (like you did above), you can try useActionState (previously named useFormState): https://react.dev/reference/react/useActionState#display-form-errorsas for documentation reference, you can check here: https://react.dev/reference/rsc/use-client#serializable-types. only values of these types are allowed as props passed from server components to client components/html elements. in the list we have
Functions that are Server Actions
signOut is a server action, so validasync () => { "use server"; await signOut() } is also a server action, so valid() => signOut() is not a server action, so not validTurkish VanOP
Thank You @joulev . I really do appreciate it. Problem solved.