Next.js Discord

Discord Forum

Server Actions behaviour

Answered
Turkish Van posted this in #help-forum
Open in Discord
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:
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.
View full answer

8 Replies

Is the sign out action have the direct 'use server' at the top?
@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.
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:

<form
    action={() => {
      const promise = signOut();
      toast.promise(promise, {
           loading: "Signing out...",
           success: "You successfully signed out",
           error: "Failed to sign out",
      });
}}>
{/*...*/}
as 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 valid
async () => { "use server"; await signOut() } is also a server action, so valid
() => signOut() is not a server action, so not valid
Turkish VanOP
Thank You @joulev . I really do appreciate it. Problem solved.