Shipkit
DocsFeaturesPricing
DocsFeaturesPricing

Shipkit DocumentationBuild memory optimizationDevelopment GuideEnvironment VariablesError HandlingFile StructureMiddlewareRebranding Guide

Vercel Deployment Checks

Development GuideCLIDependency UpdatesDevToolsFeature FlagsAI PromptsSetup RequirementsSupply chaintRPCWeb Workers

FeaturesAIAnalytics IntegrationAuthenticationCMSDatabaseEmailHapticsPaymentsComponent RegistryStorageUIVisual BuilderWaitlist Feature

Quick StartDeploymentEnvironment VariablesFile StructureSetup Wizard

Caching & Rate LimitingContent ManagementError HandlingMarkdown and MDXMiddlewareProxy-Safe Server ActionsRebranding GuideWebhook Security Implementation GuideMulti-Zone ArchitectureVercel Deployment

IntegrationsPayload CMSDataFast Analytics IntegrationGoogle Analytics IntegrationGoogle Tag Manager IntegrationPostHogStatSig IntegrationUmami Analytics IntegrationAuth.js IntegrationBetter AuthClerkStack Auth IntegrationSupabase Authentication IntegrationBuilder.io IntegrationPayload CMS IntegrationAWS S3 IntegrationResend Email IntegrationUpstash Redis IntegrationVercel Blob IntegrationLemon SqueezyPolarStripe

API RoutesComponentsAPI Route SnippetsAuthentication SnippetsComponent SnippetsForm Handling SnippetsSnippets Introduction

    Form Handling Snippets

    Server Action Form

    // src/app/(app)/posts/create/page.tsx
    import { ValidationService } from "@/server/services/validation-service";
    import { ErrorService } from "@/server/services/error-service";
    import { z } from "zod";
    import { revalidatePath } from "next/cache";
    
    const schema = z.object({
      title: z.string().min(1, "Title is required"),
      content: z.string().optional(),
    });
    
    async function createPost(formData: FormData) {
      "use server";
    
      try {
        const data = Object.fromEntries(formData);
        const result = await ValidationService.validate(schema, data);
    
        if (!result.success) {
          return { error: result.error };
        }
    
        const post = await db?.post.create({
          data: result.data,
        });
    
        revalidatePath("/posts");
        return { data: post };
      } catch (error) {
        const appError = ErrorService.handleError(error);
        return { error: appError };
      }
    }
    
    export default function CreatePostPage() {
      return (
        <form action={createPost}>
          <input
            type="text"
            name="title"
            placeholder="Title"
            required
          />
          <textarea
            name="content"
            placeholder="Content"
          />
          <button type="submit">Create Post</button>
        </form>
      );
    }
    

    Client Form with React Hook Form

    // src/components/forms/post-form.tsx
    "use client";
    
    import { useForm } from "react-hook-form";
    import { zodResolver } from "@hookform/resolvers/zod";
    import { z } from "zod";
    import { toast } from "sonner";
    import { Button } from "@/components/ui/button";
    import { Input } from "@/components/ui/input";
    import { Textarea } from "@/components/ui/textarea";
    import {
      Form,
      FormControl,
      FormField,
      FormItem,
      FormLabel,
      FormMessage,
    } from "@/components/ui/form";
    
    const formSchema = z.object({
      title: z.string().min(1, "Title is required"),
      content: z.string().optional(),
    });
    
    type FormValues = z.infer<typeof formSchema>;
    
    export function PostForm() {
      const form = useForm<FormValues>({
        resolver: zodResolver(formSchema),
        defaultValues: {
          title: "",
          content: "",
        },
      });
    
      async function onSubmit(data: FormValues) {
        try {
          const response = await fetch("/api/posts", {
            method: "POST",
            body: JSON.stringify(data),
          });
    
          const result = await response.json();
          if (!response.ok) {
            throw new Error(result.error?.message || "Something went wrong");
          }
    
          toast.success("Post created successfully");
          form.reset();
        } catch (error) {
          toast.error(error instanceof Error ? error.message : "Something went wrong");
        }
      }
    
      return (
        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
            <FormField
              control={form.control}
              name="title"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Title</FormLabel>
                  <FormControl>
                    <Input {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
    
            <FormField
              control={form.control}
              name="content"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Content</FormLabel>
                  <FormControl>
                    <Textarea {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
    
            <Button type="submit" disabled={form.formState.isSubmitting}>
              {form.formState.isSubmitting ? "Creating..." : "Create Post"}
            </Button>
          </form>
        </Form>
      );
    }
    

    File Upload Form

    // src/components/forms/upload-form.tsx
    "use client";
    
    import { useState } from "react";
    import { toast } from "sonner";
    import { Button } from "@/components/ui/button";
    import { Progress } from "@/components/ui/progress";
    
    export function UploadForm() {
      const [progress, setProgress] = useState(0);
      const [isUploading, setIsUploading] = useState(false);
    
      async function handleSubmit(formData: FormData) {
        try {
          setIsUploading(true);
          setProgress(0);
    
          const response = await fetch("/api/upload", {
            method: "POST",
            body: formData,
          });
    
          if (!response.ok) {
            const error = await response.json();
            throw new Error(error.message || "Upload failed");
          }
    
          toast.success("File uploaded successfully");
        } catch (error) {
          toast.error(error instanceof Error ? error.message : "Upload failed");
        } finally {
          setIsUploading(false);
          setProgress(0);
        }
      }
    
      return (
        <form action={handleSubmit}>
          <input
            type="file"
            name="file"
            accept="image/*"
            required
            disabled={isUploading}
          />
    
          <Button type="submit" disabled={isUploading}>
            {isUploading ? "Uploading..." : "Upload"}
          </Button>
    
          {isUploading && (
            <Progress value={progress} className="mt-2" />
          )}
        </form>
      );
    }