My fieldValues
are as follows:
export const schema = z.object({
urls: z
.array(
z.object({
path: z.string().url(),
main: z.boolean(),
id: z.string(),
})
)
.min(1)
.max(3)
.refine(
(urls) => {
const result = urls.some((url) => url.main);
console.log("refine", result);
return result;
},
{
message: "at least there is a main site!",
}
),
});
There is a custom validation logic, the logic is that urls
must have a url
marked as the main site.
But I found that the built-in min()
and max()
work well, and although my custom validation rules are triggered, error.message
is not updated.
Form.tsx
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import RemoveCircleTwoToneIcon from "@mui/icons-material/RemoveCircleTwoTone";
import { Button } from "@mui/material";
import RHFTextField from "./RHFTextField";
import RHFSwitch from "./RHFSwitch";
const Form = () => {
const { control } = useFormContext<Schema>();
const { append, fields, remove } = useFieldArray({
control,
name: "urls",
});
return (
<Controller
name="urls"
control={control}
render={({ field, fieldState: { error } }) => {
return (
<Stack spacing={1}>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant={"h6"}>urls</Typography>
<Typography variant={"body2"} color={"error"}>
{error?.message}
</Typography>
</Stack>
{fields.map((url, index) => (
<Stack key={url.id} direction={"row"}>
<div className="pr-1 h-[38px] flex items-center">
<RemoveCircleTwoToneIcon
color={"error"}
onClick={() => {
remove(index);
}}
/>
</div>
<RHFTextField name={`urls.${index}.path`} className="flex-1" />
<RHFSwitch name={`urls.${index}.main`} />
</Stack>
))}
<Button
variant="text"
onClick={() => {
append({
path: "",
main: false,
id: window.crypto.randomUUID(),
});
}}
>
add url
</Button>
</Stack>
);
}}
/>
);
};
export default Form;
https://codesandbox.io/p/sandbox/react-hook-form-1-wwqnkk?file=%2Fsrc%2Fschema.ts
add url
button.refine true
, indicating that the verification passed, but error?.message
not updated.You can use trigger
method to trigger "urls"
path validation on RHFSwitch
component changes:
const RHFSwitch = memo(({ label, name, ...props }: Props) => {
const { control, trigger } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({
field: { value, onChange, ...field },
fieldState: { error },
}) => (
<Stack direction={"row"} justifyContent={"space-between"}>
<Typography variant="h6">{label}</Typography>
<Switch
checked={value}
onChange={(e) => {
onChange(e);
trigger("urls");
}}
{...field}
{...props}
/>
</Stack>
)}
/>
);
});
Explanation
The errors handled by zod's refine
method are registered in the "urls"
path of the errors
object, which you can find inside formState
.
The "urls"
path validations are only triggered when you add/remove an item to the fields array.
I'm not sure, but I guess RHF does this on purpose, to avoid unneeded re-rendering of stuff.