I was writing an article on React 19 forms and Zod, and before submission I decided to do one more round of googling, if something similar already exists... and found your wonderful article.
Fun thing is that I ended up with almost exactly the same implementation as you, almost line by line the same where it matters.
So I'd like to share how I implemented client side validation, it's just a few more lines of code to what you already have.
If you extract validation from server action into a validation function, you can call it on client side as well, before calling server action ;)
function validate(formData) {
const formDataObj = Object.fromEntries(formData);
const { data, error } = addItemSchema.safeParse(formDataObj);
if (error) {
return { formData, errors: error.flatten().fieldErrors };
}
return { formData, data };
}
Just make sure it's not in actions file, as it's marked as "use server";, probably types.ts is the right place for it.
So your action becomes:
export async function addItemAction(
_: AddItemState,
formData: FormData,
): Promise<AddItemState> {
const res = validate(formData);
if (res.error) return res;
addItem(res.data);
revalidatePath("/");
return {};
}
and on client side:
const [state, formAction] = useActionState<AddItemState, FormData>(
async (prev, formData) => {
const res = validate(formData);
if (res.error) return res;
return addItemAction(prev, formData);
},
{},
);
This way you save a roundtrip to server and apply exactly the same validation in both realms.
I was writing an article on React 19 forms and Zod, and before submission I decided to do one more round of googling, if something similar already exists... and found your wonderful article.
Fun thing is that I ended up with almost exactly the same implementation as you, almost line by line the same where it matters.
So I'd like to share how I implemented client side validation, it's just a few more lines of code to what you already have.
If you extract validation from server action into a validation function, you can call it on client side as well, before calling server action ;)
Just make sure it's not in actions file, as it's marked as
"use server";, probablytypes.tsis the right place for it.So your action becomes:
and on client side:
This way you save a roundtrip to server and apply exactly the same validation in both realms.