this is my implementation with RHF + Zod for both Client + Server side validation with server actions :
"use client";
....
const formSchema = z.object({
.....
});
const [loading, setLoading] = useState(false);
const form = useForm<ProductFormValues>({
resolver: zodResolver(formSchema),
defaultValues,
});
const clientAction = async (formData: FormData) => {
try {
// trigger client-side validation
form.trigger();
if (!form.formState.isValid) {
return;
}
setLoading(true);
const {
errors, // from zod validation from the server
message = "",
data, // result of successful mutation
} = await updateProduct(product.id.toString(), formData);
//Show toast with a success message
toast.custom(`... ${data.title} is Created 🚀`);
} catch (error: any) {
toast.error("Something went wrong.", JSON.strinfy(errors));
} finally {
setLoading(false);
}
};
...
<Form {...form}>
<form action={clientAction} className="w-full space-y-8">
<div className="gap-8 md:grid md:grid-cols-3">
...
- server action :
"use server"
const formSchema = z.object({
....
});
/**
** Creates a new product.
*
* @param {FormData} formData - The form data of the new product.
* @returns {Promise<Product>} A promise that resolves to the created product.
*/
export async function createProduct(formData: FormData) {
try {
const validatedFields = formSchema.safeParse({
...
});
// Return early if the form data is invalid
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
};
}
const res = await axios.post(
`${API_URL}/products`,
validatedFields.data,
);
revalidatePath("/admin/products");
revalidatePath("/products");
return { message: "Create Product successfully", data: res.data };
} catch (error) {
console.error(`Failed to create product:`, error?.response?.data);
return {
errors: {
title: "There was an error with this title",
description: "There was an error with this description",
...
},
message: error?.response?.data,
};
}
}