Demo
Apps
Dashboard

Forms

Learn how to use forms in the starter kit.

Forms are a crucial part of web applications. The starter kit uses react-hook-form for form state management, zod for schema validation and next-safe-action for secure server actions.

Creating the Form Component

We will create a form that allows users to submit their details. The form will use react-hook-form with Controller, zod for validation, and shadcn/ui components.

1. Define the zod schema

Create a schema under ~/schemas/items/add-item-schema.ts to define the form structure and validation rules.

apps/dashboard/schemas/items/add-item-schema.ts
import { z } from 'zod';export const addItemSchema = z.object({  name: z.string().min(2, 'Name must be at least 2 characters'),  email: z.string().email('Invalid email address'),  phone: z.string().min(10, 'Invalid phone number')});export type AddItemSchema = z.infer<typeof addItemSchema>;

2. Create the Form Component

Use react-hook-form with zod and shadcn/ui components.

apps/dashboard/components/items/add-item-form.tsx
'use client';import * as React from 'react';import { Button } from '@workspace/ui/components/button';import {  Form,  FormControl,  FormField,  FormItem,  FormLabel,  FormMessage,  FormProvider} from '@workspace/ui/components/form';import { Input } from '@workspace/ui/components/input';import { toast } from '@workspace/ui/components/sonner';import { Controller, type SubmitHandler } from 'react-hook-form';import { addItem } from '~/actions/items/addItem';import { useZodForm } from '~/hooks/use-zod-form';import { addItemSchema, type AddItemSchema } from '~/schemas/items/addItem';export function AddItemForm(): React.JSX.Element {  const methods = useZodForm({    schema: addItemSchema,    mode: 'onSubmit'  });  const onSubmit: SubmitHandler<AddItemSchema> = async (values) => {    // do something with it  };  return (    <FormProvider {...methods}>      <form        className="space-y-4"        onSubmit={methods.handleSubmit(onSubmit)}      >        <FormField          control={methods.control}          name="name"          render={({ field }) => (            <FormItem>              <FormLabel>Name</FormLabel>              <FormControl>                <Input {...field} />              </FormControl>              <FormMessage />            </FormItem>          )}        />        <FormField          control={methods.control}          name="email"          render={({ field }) => (            <FormItem>              <FormLabel>Email</FormLabel>              <FormControl>                <Input                  type="email"                  {...field}                />              </FormControl>              <FormMessage />            </FormItem>          )}        />        <FormField          control={methods.control}          name="phone"          render={({ field }) => (            <FormItem>              <FormLabel>Phone</FormLabel>              <FormControl>                <Input                  type="tel"                  {...field}                />              </FormControl>              <FormMessage />            </FormItem>          )}        />        <Button          type="submit"          disabled={methods.formState.isSubmitting}        >          Submit        </Button>      </form>    </FormProvider>  );}

Validation errors will be renderd in <FormMessage /> component.

Handling Server Actions

Let's create a server action to handle the form submission.

1. Define the Server Action

Create an action under ~/actions/items/add-item.ts to handle form submissions securely.

apps/dashboard/actions/items/add-item.ts
'use server';import { revalidateTag } from 'next/cache';import { db } from '@workspace/database/client';import { itemTable } from '@workspace/database/schema';import { Caching, OrganizationCacheKey } from '~/data/caching';import { addItemSchema } from '~/schemas/items/addItem';export const addItem = createSafeAction(  addItemSchema,  async ({ parsedInput, ctx }) => {    await db.insert(itemTable).values({      name: parsedInput.name,      email: parsedInput.email,      phone: parsedInput.phone    });    revalidateTag(      Caching.createOrganizationTag(        OrganizationCacheKey.ItemList,        ctx.organization.id      )    );  });

2. Connect the Form to the Server Action

We already used addItem in our AddItemForm component. Now, when the form is submitted, it will be validated on both the client and server securely, and caching will be handled efficiently.

apps/dashboard/components/items/add-item-form.tsx
const onSubmit: SubmitHandler<AddItemSchema> = async (values) => {  const result = await addItem(data);  if (!result?.serverError && !result?.validationErrors) {    toast.success('Item added successfully');  } else {    toast.error('Failed to add item');  }};