Reputation: 133
I want to make a dynamic Form that show different input fields based on a Boolean value.
When calling useForm of the react-hook-form package I use a zod object to validate my inputs. This object is a union of two other zod objects (one for each Boolean case). This results in the error type only showing the shared options in TypeScript.
When running the code everything works and the errors show up. But I still don't like that type safety isn't provided anymore. Is there a fix to this problem?
import { zodResolver } from "@hookform/resolvers/zod"
import React from "react"
import { useForm } from "react-hook-form"
import { z } from "zod"
const TrueSchema = z.object({
isTrue: z.literal("true"),
trueInput: z.string().min(1, "Required, true"),
})
const FalseSchema = z.object({
isTrue: z.literal("false"),
falseInput: z.string().min(1, "Required, false"),
})
const FormSchema = z.discriminatedUnion("isTrue", [TrueSchema, FalseSchema])
type FormSchemaType = z.infer<typeof FormSchema>
function Example() {
const {
register,
watch,
handleSubmit,
formState: { errors },
} = useForm<FormSchemaType>({
resolver: zodResolver(FormSchema),
defaultValues: {
isTrue: "false",
},
})
const onSubmit = handleSubmit((data) => console.log("data", data))
return (
<form onSubmit={onSubmit}>
<label htmlFor="radioTrue">
<input
type="radio"
id="radioTrue"
value={"true"}
{...register("isTrue")}
/>
True
</label>
<label htmlFor="radioFalse">
<input
type="radio"
id="radioFalse"
value={"false"}
{...register("isTrue")}
/>
False
</label>
{watch("isTrue") === "true" && (
<>
<input
type="text"
placeholder="Bool is true"
{...register("trueInput")}
/>
{errors.trueInput && <p>{errors.trueInput.message}</p>} {/* Only error.isTrue available here */}
</>
)}
{watch("isTrue") === "false" && (
<>
<input
type="text"
placeholder="Bool is false"
{...register("falseInput")}
/>
{errors.falseInput && <p>{errors.falseInput.message}</p>} {/* Only error.isTrue available here */}
</>
)}
<input type="submit" />
</form>
)
}
export default Example
The packages I use:
"@hookform/resolvers": "^2.9.10",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.38.0",
"zod": "^3.19.1"
Upvotes: 6
Views: 5883
Reputation: 121
This is a limitation from the current design of React Hook Form. You can see the discussions about it, here:
For now, workarounds are needed, enforcing the right type:
import { FieldErrors, useForm } from 'react-hook-form'
const {
formState: { errors },
} = useForm<UnionSchema>()
<Component>
{(() => {
if (condition) {
const formErrors: FieldErrors<TheRightSchema> = errors
// formErrors knows the right fields.
return (
<>
// Your inputs
</>
)
}
})()}
</Component>
or
"email" in errors && errors.email
Upvotes: 5