---
description: Writing Forms with Shadcn UI, Server Actions, Zod
globs: apps/**/*.tsx,packages/**/*.tsx
alwaysApply: false
---

# Forms

- Use React Hook Form for form validation and submission.
- Use Zod for form validation.
- Use the `zodResolver` function to resolve the Zod schema to the form.
- Use Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) for server-side code handling
- Use Sonner for writing toasters for UI feedback
- Never add generics to `useForm`, use Zod resolver to infer types instead

Follow the example below to create all forms:

## Define the schema

Zod schemas should be defined in the `schema` folder and exported, so we can reuse them across a Server Action and the client-side form:

```tsx
// _lib/schema/create-note.schema.ts
import { z } from 'zod';

export const CreateNoteSchema = z.object({
  title: z.string().min(1),
  content: z.string().min(1),
});
```

## Create the Server Action

Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) can help us create endpoints for our forms.

```tsx
'use server';

import { z } from 'zod';

import { enhanceAction } from '@kit/next/actions';

import { CreateNoteSchema } from '../schema/create-note.schema';

export const createNoteAction = enhanceAction(
  async function (data, user) {
    // 1. "data" has been validated against the Zod schema, and it's safe to use
    // 2. "user" is the authenticated user

    // ... your code here
    return {
      success: true,
    };
  },
  {
    auth: true,
    schema: CreateNoteSchema,
  },
);
```

## Create the Form Component

Then create a client component to handle the form submission:

```tsx
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Textarea } from '@kit/ui/textarea';
import { Input } from '@kit/ui/input';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
import { toast } from '@kit/ui/sonner';
import { useTranslation } from 'react-i18next';

import { CreateNoteSchema } from '../_lib/schema/create-note.schema';

export function CreateNoteForm() {
  const [pending, startTransition] = useTransition();
  const { t } = useTranslation();

  const form = useForm({
    resolver: zodResolver(CreateNoteSchema),
    defaultValues: {
      title: '',
      content: '',
    },
  });

  const onSubmit = (data) => {
    startTransition(async () => {
      await toast.promise(createNoteAction(data), {
        loading: t('notes:creatingNote`),
        success: t('notes:createNoteSuccess`),
        error: t('notes:createNoteError`)
      })
    });
  };

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <Form {...form}>
        <FormField name={'title'} render={({ field }) => (
          <FormItem>
            <FormLabel>
              <span className={'text-sm font-medium'}>Title</span>
            </FormLabel>

            <FormControl>
              <Input
                type={'text'}
                className={'w-full'}
                placeholder={'Title'}
                {...field}
              />
            </FormControl>

            <FormMessage />
          </FormItem>
        )} />

        <FormField name={'content'} render={({ field }) => (
          <FormItem>
            <FormLabel>
              <span className={'text-sm font-medium'}>Content</span>
            </FormLabel>

            <FormControl>
              <Textarea
                className={'w-full'}
                placeholder={'Content'}
                {...field}
              />
            </FormControl>

            <FormMessage />
          </FormItem>
        )} />

        <button disabled={pending} type={'submit'} className={'w-full'}>
          Submit
        </button>
      </Form>
    </form>
  );
}
```

Always use `@kit/ui` for writing the UI of the form.
