145 lines
3.4 KiB
Plaintext
145 lines
3.4 KiB
Plaintext
---
|
|
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.
|
|
|
|
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
|
|
|
|
```tsx
|
|
// _lib/server/server-actions.ts
|
|
'use server';
|
|
|
|
import { z } from 'zod';
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
import { CreateNoteSchema } from '../schema/create-note.schema';
|
|
|
|
const CreateNoteSchema = z.object({
|
|
title: z.string().min(1),
|
|
content: z.string().min(1),
|
|
});
|
|
|
|
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
|
|
// _components/create-note-form.tsx
|
|
'use client';
|
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { useForm } from 'react-hook-form';
|
|
import { z } from 'zod';
|
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
|
|
|
|
import { CreateNoteSchema } from '../_lib/schema/create-note.schema';
|
|
|
|
export function CreateNoteForm() {
|
|
const [pending, startTransition] = useTransition();
|
|
|
|
const form = useForm({
|
|
resolver: zodResolver(CreateNoteSchema),
|
|
defaultValues: {
|
|
title: '',
|
|
content: '',
|
|
},
|
|
});
|
|
|
|
const onSubmit = (data) => {
|
|
startTransition(async () => {
|
|
try {
|
|
await createNoteAction(data);
|
|
} catch {
|
|
// handle error
|
|
}
|
|
});
|
|
};
|
|
|
|
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. |