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.
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
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
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