I have a route to edit my data as /students/update/[id], and another one to create a student as /students/create. I also have a route at api/students so that I can send POST requests to that endpoint with my form data and update my database.
For /students/create, I put in the form directly in page.tsx because I did not need any other components.
"use client"
import { useRouter } from 'next/navigation';
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { NavMenu } from '@/components/navmenu';
import { Button } from '@/components/ui/button';
import {
Form,
FormLabel,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
const formSchema = z.object({
email_address: z.string().min(1, { message: "This has to be filled" })
.email("This is not a valid email"),
full_name: z.string(),
nickname: z.string(),
gender: z.enum(["male", "female", "prefer not to say", "N/A"]),
date_of_birth: z.optional(z.string().date()),
age: z.optional(z.coerce.number()),
telegram: z.optional(z.string().startsWith('@')),
academic_level: z.optional(z.enum(["undergraduate", "graduate", "basic_edu", "N/A"])),
university: z.optional(z.string()),
university_category: z.optional(z.string()),
residency: z.optional(z.string()),
isDeleted: z.string(),
})
export default function Home() {
const router = useRouter();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
full_name: "",
nickname: "",
gender: "N/A",
isDeleted: "false",
},
})
async function onSubmit(values: z.infer<typeof formSchema>) {
let formData = new FormData();
for (const [key, value] of Object.entries(values)) {
formData.append(key as string, value as string);
}
const response = await fetch('/api/students/', {
method: 'POST',
body: formData
});
if (response.ok) {
router.push('/students');
} else {
console.error('Student creation failed', await response.json());
router.refresh();
}
}
return (
<>
<NavMenu />
<div className="max-w-1/2 px-10 py-10">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<div className="flex gap-10">
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="email_address"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="email address" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="full_name"
render={({ field }) => (
<FormItem>
<FormLabel>Full name</FormLabel>
<FormControl itemType="text">
<Input placeholder="full name" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="nickname"
render={({ field }) => (
<FormItem>
<FormLabel>Nickname</FormLabel>
<FormControl itemType="text">
<Input placeholder="nickname" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="gender"
render={({ field }) => (
<FormItem>
<FormLabel>Gender</FormLabel>
<FormControl itemType="text">
<Input placeholder="N/A/male/female/prefer not to say" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col justify-end gap-4">
<FormField
control={form.control}
name="age"
render={({ field }) => (
<FormItem>
<FormLabel>Age</FormLabel>
<FormControl itemType="number">
<Input placeholder="age" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="telegram"
render={({ field }) => (
<FormItem>
<FormLabel>Telegram</FormLabel>
<FormControl itemType="text">
<Input placeholder="@id" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="academic_level"
render={({ field }) => (
<FormItem>
<FormLabel>Academic Level</FormLabel>
<FormControl itemType="text">
<Input placeholder="Check the values" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button className="bg-slate-200 w-full" type="submit">Create</Button>
</div>
<div className="flex flex-col gap-10">
<FormField
control={form.control}
name="university_category"
render={({ field }) => (
<FormItem>
<FormLabel>University Category</FormLabel>
<FormControl itemType="text">
<Input placeholder="Check the values" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="university"
render={({ field }) => (
<FormItem>
<FormLabel>University</FormLabel>
<FormControl itemType="text">
<Input placeholder="Check the values" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="residency"
render={({ field }) => (
<FormItem>
<FormLabel>Region</FormLabel>
<FormControl itemType="text">
<Input placeholder="region" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</form>
</Form>
</div>
</>
)
}
For students/update/[id], in contrast, I want to populate the form with existing data from the database so I made a query to database with the student id in page.tsx and import the client side Form component there like this.
import { fetchStudentById } from '@/app/lib/actions';
import { Student } from '@prisma/client';
import StudentUpdateForm from './studentUpdateForm';
import { NavMenu } from '@/components/navmenu';
export default async function Page({ params }: { params: { id: number } }) {
const student: Student = await fetchStudentById(params.id);
return (
<>
<NavMenu />
<StudentUpdateForm student={student} />
</>
);
}
The StudentUpdateForm looks like this.
"use client"
import { useRouter } from 'next/navigation';
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from '@/components/ui/button';
import {
Form,
FormLabel,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
const formSchema = z.object({
id: z.coerce.number(),
email_address: z.string().min(1, { message: "This has to be filled" })
.email("This is not a valid email"),
full_name: z.string(),
nickname: z.string(),
gender: z.optional(z.union([z.literal("male"), z.literal("female"), z.literal("prefer not to say"), z.literal("N/A")])),
date_of_birth: z.optional(z.string().date()),
age: z.optional(z.coerce.number()),
telegram: z.optional(z.string().startsWith('@')),
academic_level: z.optional(z.string().or(z.enum(["undergraduate", "graduate", "basic_edu", "N/A"]))),
university: z.optional(z.string()),
university_category: z.optional(z.string()),
residency: z.optional(z.string()),
isDeleted: z.string(),
})
interface StudentUpdateFormPropType {
student: {
id: number,
email_address: string | null,
full_name: string | null,
nickname: string | null,
date_of_birth: Date | null,
age: number | null,
gender: "male" | "female" | "prefer not to say" | "N/A" | null,
telegram: string | null,
academic_level: string | null,
university: string | null,
university_category: string | null,
residency: string | null,
isDeleted: string | null,
},
}
export default function StudentUpdateForm(props: StudentUpdateFormPropType) {
const router = useRouter();
const student = props.student;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
id: student.id,
email_address: student.email_address || "",
full_name: student.full_name || "",
nickname: student.nickname || "",
gender: student.gender || "N/A",
date_of_birth: student.date_of_birth ?
[student.date_of_birth.getFullYear(), student.date_of_birth.getMonth(), student.date_of_birth.getDay() + 1].join('/')
: "",
age: student.age || undefined,
telegram: student.telegram != undefined ? student.telegram : "",
academic_level: student.academic_level || "N/A",
university: student.university || "",
university_category: student.university_category || "",
residency: student.residency || "",
isDeleted: student.isDeleted || "",
},
})
async function onSubmit(values: z.infer<typeof formSchema>) {
alert("clicked");
let formData = new FormData();
for (const [key, value] of Object.entries(values)) {
formData.append(key as string, value as string);
}
const response = await fetch('/api/students/', {
method: 'POST',
body: formData
});
if (response.ok) {
router.push('/students');
} else {
console.error('Student creation failed', await response.json());
router.refresh();
}
}
return (
<>
<div className="max-w-1/2 px-10 py-10">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<div className="flex gap-10">
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem>
<FormLabel>ID</FormLabel>
<FormControl>
<Input disabled={true} placeholder={"SSM-" + student?.id.toString()} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email_address"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder={student.email_address || ""} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="full_name"
render={({ field }) => (
<FormItem>
<FormLabel>Full name</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.full_name || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="nickname"
render={({ field }) => (
<FormItem>
<FormLabel>Nickname</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.nickname || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col justify-end gap-4">
<FormField
control={form.control}
name="gender"
render={({ field }) => (
<FormItem>
<FormLabel>Gender</FormLabel>
<FormControl itemType="text">
<Input type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="age"
render={({ field }) => (
<FormItem>
<FormLabel>Age</FormLabel>
<FormControl itemType="number">
<Input type="number" placeholder={student.age ? student.age.toString() : ""} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="telegram"
render={({ field }) => (
<FormItem>
<FormLabel>Telegram</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.telegram || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="academic_level"
render={({ field }) => (
<FormItem>
<FormLabel>Academic Level</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.academic_level || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button className="bg-slate-200 w-full" type="submit">Update</Button>
</div>
<div className="flex flex-col gap-10">
<FormField
control={form.control}
name="university_category"
render={({ field }) => (
<FormItem>
<FormLabel>University Category</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.university_category || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="university"
render={({ field }) => (
<FormItem>
<FormLabel>University</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.university || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="residency"
render={({ field }) => (
<FormItem>
<FormLabel>Region</FormLabel>
<FormControl itemType="text">
<Input placeholder={student.residency || ""} type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</form>
</Form>
</div>
</>
)
}
at api/students/route.ts, I try to differentiate two POST requests coming from /students/create and students/update/[id] with if statement, like this
import { NextRequest, NextResponse } from "next/server";
import { createStudent, updateStudentById } from "@/app/lib/actions";
export async function POST(request: NextRequest) {
const formData = await request.formData();
console.log(formData.get("id"));
if (!formData.get('id')) {
const studentData = {
"email_address": formData.get("email_address") as string,
"full_name": formData.get("full_name") as string || "",
"nickname": formData.get("nickname") as string || "",
"gender": formData.get("gender") as string || "N/A",
"age": Number(formData.get("age")) as number || null,
"telegram": formData.get("telegram") as string || "N/A",
"academic_level": formData.get("academic_level") as string || "N/A",
"university": formData.get("university") as string || "N/A",
"university_category": formData.get("university_category") as string || "N/A",
"residency": formData.get("residency") as string || "N/A",
"isDeleted": formData.get("isDeleted") as string || "N/A",
}
const id = await createStudent(studentData);
} else {
const studentData = {
"email_address": formData.get("email_address") as string,
"full_name": formData.get("full_name") as string || "",
"nickname": formData.get("nickname") as string || "",
"gender": formData.get("gender") as string || "N/A",
"age": Number(formData.get("age")) as number || null,
"telegram": formData.get("telegram") as string || "N/A",
"academic_level": formData.get("academic_level") as string || "N/A",
"university": formData.get("university") as string || "N/A",
"university_category": formData.get("university_category") as string || "N/A",
"residency": formData.get("residency") as string || "N/A",
"isDeleted": formData.get("isDeleted") as string || "N/A",
}
const id = Number(formData.get("id"));
const student = await updateStudentById(id, studentData);
}
return new NextResponse();
}
So when I click Create button in /students/create, it works as expected, it sent a POST request to the api and returns an OK, and add a new entry to the database. However, when I click Update in /students/update/[id], the onSubmit is not supposedly firing, judging from the console.log not working either in the onSubmit callback. What might be the reason? Sorry if the question is too long.