Server Actions


Server actions in client component

const Page = async () => {
  const cookie = cookies().get('user')?.value;
  const user = cookie && JSON.parse(cookie);

  return <Form user={user} />;
};

app/server-actions/client/_actions/register.tsx

'use server';

export const register = async (formData: FormData) => {
  const name = formData.get('name');

  if (!name) return;

  await sleep(3000);

  cookies().set({
    name: 'user',
    value: JSON.stringify({ name }),
    httpOnly: true,
  });

  revalidatePath('/parallel/me');
};

app/server-actions/client/_actions/logout.tsx

'use server';

export const logout = async () => {
  cookies().set({ name: 'user', value: '', httpOnly: true });

  revalidatePath('/parallel/me');
};

app/server-actions/client/_components/Form.tsx

'use client';

import { register } from '@/app/server-actions/client/_actions/register';
import { logout } from '@/app/server-actions/client/_actions/logout';

export const Form = ({ user }: { user: { name: string } }) => {
  if (!user?.name) {
    return (
      <div>
        <h2>Server actions in client component</h2>

        <form action={register}>
          <FormContent>
            <label>
              Enter your name:
              <input type="text" name={'name'} />
            </label>
            <button>Submit</button>
          </FormContent>
        </form>
      </div>
    );
  }

  return (
    <form action={logout}>
      <h3>Hello, {user.name}!</h3>
      <button>Logout</button>
    </form>
  );
};

const FormContent = ({ children }: PropsWithChildren) => {
  const { pending } = useFormStatus(); // this works only inside <form> element

  return (
    <div
      style={
        pending
          ? ({
              opacity: 0.5,
              filter: 'contrast(0.5)',
              userSelect: 'none',
            } satisfies CSSProperties)
          : {}
      }
    >
      {children}
    </div>
  );
};