Reputation: 17369
I am using custom register
in react-hook-form
, and I am unable to get formState.isValid
to be true
when I enter text into the input (and so satisfy the required
condition).
Here is an example code:
interface FormValues {
readonly value: string;
}
export default function App() {
console.log("rendering");
const form: UseFormMethods<FormValues> = useForm<FormValues>({
mode: "onChange",
reValidateMode: "onChange",
defaultValues: { value: "" }
});
useEffect(() => {
form.register({ name: "value" }, { required: true });
}, [form, form.register]);
const { isValid } = form.formState;
const value = form.watch("value");
return (
<div>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
form.setValue("value", e.target.value);
}}
/>
<div>IsValid: {JSON.stringify(isValid)}</div>
<div>Errors: {JSON.stringify(form.errors)}</div>
<div>Value: {JSON.stringify(value)}</div>
</div>
);
}
The question is sepcifically for this type of register
use, not other types (ref
or Controller
).
Here is a full example.
Does someone know why this is the case, what am I missing?
Additionally, but this is less relevant - does anyone know why rendering is triggered twice for each input change?
After discussion with Dennis Vash, there was some progress on this issue, but it is still not resolved.
The docs at https://react-hook-form.com/api/#setValue actually do specify an option for triggering validation:
(name: string, value: any, shouldValidate?: boolean) => void
You can also set the shouldValidate parameter to true in order to trigger a field validation. eg: setValue('name', 'value', true)
At the time when I am writing this, the docs refer to version 5 of react-form-hook
, I am actually using 6.0.0-rc.5
so the signature changed a bit to something similar to the following:
(name: string, value: any, { shouldValidate: boolean; shouldDirty: boolean; }) => void
However, in an example that I have when using shouldValidate: true
, I get an infinite loop:
interface FormValues {
readonly value: string;
}
export default function App() {
console.log("rendering");
const form: UseFormMethods<FormValues> = useForm<FormValues>({
mode: "onChange",
reValidateMode: "onChange",
defaultValues: { value: "" }
});
useEffect(() => {
form.register({ name: "value" }, { required: true, minLength: 1 });
}, [form, form.register]);
const { isValid } = form.formState;
const value = form.getValues("value");
return (
<div>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
form.setValue("value", e.target.value, {
shouldValidate: true
});
}}
/>
<div>IsValid: {JSON.stringify(isValid)}</div>
<div>Errors: {JSON.stringify(form.errors)}</div>
<div>Value: {JSON.stringify(value)}</div>
</div>
);
}
The loop is occurring when isValid
is true
, but stops when it is false
.
You can try it out here. Entering a key will start the continuous re-rendering, and clearing the input will stop the loop...
Upvotes: 32
Views: 41452
Reputation: 442
Referring to https://github.com/react-hook-form/react-hook-form/issues/2147
You need to set the mode to onChange or onBlur
const { register, handleSubmit, formState: { errors, isDirty, isValid }} = useForm({ mode: 'onChange' });
In this case, the 'isValid' will work as expected.
Upvotes: 23
Reputation: 53894
The component rendered twice because of React.StrictMode
.
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions: ...
Also, in order to validate you need to submit the form (try pressing enter
), if you on onChange
mode without using a reference, you should use triggerValidation
instead.
export default function App() {
const {
register,
formState,
watch,
errors,
setValue,
handleSubmit
}: UseFormMethods<FormValues> = useForm<FormValues>();
useEffect(() => {
register(
{ name: "firstName", type: "custom" },
{ required: true, min: 1, minLength: 1 }
);
console.log("called");
}, [register]);
const { isValid } = formState;
const values = watch("firstName");
const onSubmit = (data, e) => {
console.log("Submit event", e);
console.log(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setValue("firstName", e.target.value);
}}
/>
{/*<button>Click</button>*/}
<div>IsValid: {JSON.stringify(isValid)}</div>
<div>Errors: {JSON.stringify(errors)}</div>
<div>Form value: {JSON.stringify(values)}</div>
</form>
);
}
Upvotes: 5