- react hook form and it's zod integration is a better way IMHO (https://github.com/jsun969/react-hook-form-antd)
- Might only work in Antd v4
- Don't add
rules
on the schema relatedForm.Item
components. - Might not do everything you need (e.g. async?)
- May have bugs. But what doesn't 🤣
The hook found in zodAntdFormAdapter.tsx
might be useful when trying to
run zod schema validation when the form is changed by the user.
It takes the form instance, the schema, and onFinish
and onFinishFailed
callback functions. The expected onFinish
callback differs a bit from the
callback you would normally pass to the Form
component as it tries to infer
the type of the values from the passed in schema. onFinishFailed
has the
same signature as the regular form prop though.
The hook basically adds a watch callback to the form store that runs the schema validation and sets the schema errors on the matching form fields whenever the form changes.
It returns a props object which is meant to be spread into the form props. The
props provide wrapped onFinish
and onFinishFailed
callbacks that take care of
calling your callbacks, taking the schema validation into account.
For convenience, the props also contain the form
prop already set.
import { countBy } from "lodash";
import { z } from "zod";
import { Button, Card, Form, Input } from "antd";
import { useZodAntdFormAdapter } from "./zodAntdFormAdapter";
const initialValues = {
name: "foo",
items: [{ id: "foo" }],
};
const schema = z.object({
name: z.string().min(3),
items: z.array(
z.object({
id: z.string().min(3),
})
).superRefine((items, ctx) => {
const idCounts = countBy(items, "id");
items.forEach(({ id }, index) => {
if (idCounts[id] > 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "The item id has to be unique",
path: [index, "id"],
});
}
});
}),
});
function MyForm() {
const [form] = Form.useForm();
const formAdapter = useZodAntdFormAdapter({
form,
schema,
onFinish(values) {
console.log("my onFinish", { values });
},
onFinishFailed(error) {
console.log("my onFinishFailed", { error });
},
});
return (
<Form
{...formAdapter}
initialValues={initialValues}
>
<Form.Item name={["name"]} label="Name" required>
<Input />
</Form.Item>
<Form.List name={["items"]}>
{(fields, { add, remove }) => (
<Form.Item label="Items">
<Button onClick={() => add({ "id": "bar" })}>Add one</Button>
{fields.map((field) => (
<Card key={field.key}>
<Form.Item name={[field.name, "id"]} label="Item id" required>
<Input />
</Form.Item>
<Button onClick={() => remove(field.name)}>Remove</Button>
</Card>
))}
</Form.Item>
)}
</Form.List>
<Button onClick={form.submit}>Submit</Button>
</Form>
);
}
I mean, it's cool, but why we still have to pass the required parameter to the UI, can't we infer it from zod?