onClickExamples

Next Form

Handle submissions using Typescript, Next 14 with server actions.

This example uses Getform for handle submission and ui.shadcn for the re-usable form component.

Installation

Create a new Next.js project:

Terminal

npx create-next-app@latest my-app --typescript --tailwind --eslint

and/or you can init shadcn-ui to your project with:

Terminal

npx shadcn-ui@latest init

There will be three question for shadcn-ui components.json:

Terminal

⁠Which style would you like to use? › Default
⁠Which color would you like to use as base color? › Slate
⁠Do you want to use CSS variables for colors? › no / yes

and last step install form components:

Terminal

npx shadcn-ui@latest add form
npx shadcn-ui@latest add input

File Structure

This is a basic file structure for this example.

Tree

├── app
│   ├── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   └── actions.ts
├── components
│   └── ui
│       ├── form.tsx
│       ├── input.tsx
│       └── label.tsx
└── .env.local

Files

Create .env.local file at the root and write your endpoint in it. You can create endpoint at Getform

.env.local

NEXT_GETFORM_ENDPOINT=https://getform.io/f/FORM_ID

This form includes validations using zod, allowing you to easily configure the validations according to your needs.

app/page.tsx

"use client";

import { send } from "./actions";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";

import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

const formSchema = z.object({
  name: z.string().min(5, {
    message: "Your name must be at least 5 characters.",
  }),
  // Add other fields here as needed
});

export default function ProfileForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
        name: "",
    },
  });

  async function onSubmit(values: z.infer<typeof formSchema>) {
    try {
      const formData = new FormData();
      Object.entries(values).forEach(([key, value]) => {
        formData.append(key, value);
      });

      const response = await send(formData);
      console.log("Form successfully submitted", response);
    } catch (error) {
      console.error("Form submission error", error);
    }

    // loading, success etc.
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Full Name</FormLabel>
              <FormControl>
                <Input placeholder="John Doe" {...field} />
              </FormControl>
              <FormMessage />
              <FormDescription>
                Your form description here if needed
              </FormDescription>
            </FormItem>
          )}
        />
        {/* Add other fields/FormItem here */}
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  );
}

The final step is to trigger the send function on the server side.

app/actions.ts

"use server";

export async function send(formData: FormData) {
  try {
    const response = await fetch(process.env.NEXT_GETFORM_ENDPOINT!, {
      method: "POST",
      body: formData,
      headers: {
        Accept: "application/json",
      },
    });

    if (!response.ok) {
      throw new Error("Something went wrong");
    }

    return await response.json();
  } catch (error) {
    console.error("Error:", error);
    throw error;
  }
}

Completed:
Next Form

<- Home
End