Reputation: 1045
I am trying to display an error with yup
and react-hook-form
when the user selects more than 5 checkboxes without success.
Instead, the error is shown when the seventh checkbox is selected.
Here is the simplified code:
imports...
const schema = yup.object().shape({
option: yup.array().max(5)
});
function App() {
const { register, handleSubmit, errors } = useForm({
mode: "onBlur",
resolver: yupResolver(schema)
});
const [state, setState] = useState({
wasSubmitted: false
});
const submit = async (data) => {
window.alert(JSON.stringify(data));
};
if (state.wasSubmitted) {
return <p>Congrats</p>;
} else {
return (
<>
<CssBaseline />
<Container maxWidth="sm">
<Typography variant="h2" component="h1">
My form
</Typography>
<form noValidate autoComplete="off" onSubmit={handleSubmit(submit)}>
<FormControl
component="fieldset"
error={!!errors.option}
>
<FormLabel component="legend">
Please select the category or categories of books the child is
interested in:
</FormLabel>
<FormGroup>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option1"
label="Option 1"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option2"
label="Option 2"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
label="Option3"
value="Option 3"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option4"
label="Option 4"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option5"
label="Option 5"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option6"
label="Option 6"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option7"
label="Option 7"
/>
<FormControlLabel
control={<Checkbox name="option" inputRef={register} />}
value="Option8"
label="Option 8"
/>
<FormControlLabel
<FormHelperText>Up to five categories</FormHelperText>
</FormControl>
<Button
type="submit"
disableElevation
>
Submit
</Button>
</form>
</Container>
</>
);
}
}
export default App;
You can also find the project's sandbox here:
Any ideas?
Upvotes: 39
Views: 80661
Reputation: 7819
If you use onBlur
- when an input loses focus - checking the 5th option i.e 5 options checked, the blur event will be triggered 4 times only.
When you checked the 7th option, the blur event will be triggered 6 times, thus raising the error (limit of 5 exceeded)
Changing to onChange
, will make validation occur as soon as a checkbox is checked.
Upvotes: 0
Reputation: 131
If you're using controlled components onBlur might not be automatically firing. You will have to wire it manually:
useController docs
wrapper docs
export const MyControlledField = ({ fieldName }: { fieldName: string }) => {
const methods = useFormContext();
const value = useWatch({ name: fieldName });
const controller = useController({
name: fieldName,
control: methods.control,
});
return (
<TextField
value={value ?? ''}
onChange={(e) => {
methods.setValue(fieldName, e.target.value);
}}
onBlur={controller.field.onBlur}
/>
);
};
Or using a wrapped component:
export const MyWrappedField = ({ fieldName }: { fieldName: string }) => {
const methods = useFormContext();
return (
<Controller
control={methods.control}
name={fieldName}
render={({ field: { onChange, onBlur, value, name, ref }, fieldState, formState }) => (
<TextField
onBlur={onBlur}
onChange={onChange}
value={value}
inputRef={ref}
/>
)}
/>
);
};
Upvotes: 12
Reputation: 81370
As @aadlc said, the solution is to set the mode to onChange
or all
. I'll explain the reason why.
From react-hook-form API docs:
mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'
Name | Type | Description |
---|---|---|
onSubmit (Default) | string | Validation will trigger on the submit event and invalid inputs will attach onChange event listeners to re-validate them. |
onBlur | string | Validation will trigger on the blur event. |
onChange | string | Validation will trigger on the change event with each input, and lead to multiple re-renders. Warning: this often comes with a significant impact on performance. |
onTouched | string | Validation will trigger on the first blur event. After that, it will trigger on every change event. |
all | string | Validation will trigger on the blur and change events. |
In your code, the form mode is onBlur
. it means the validation is triggered on blur
event (unfocus the input). When you select the option n+1
, it triggers the blur event from the option n
.
e.g. Just before you select the 6th option (invalid), the blur
event fires from the 5th option (valid) because you're no longer focusing it, and validate from option 1-5, so you have to check the 7th option to revalidate the option from 1 to 6.
-- select up to 5 options --
select option 4
blur event fires from option 4 -> validate -> pass
select option 5
blur event fires from option 5 -> validate -> pass
select option 6
blur event fires from option 6 -> validate -> fail
select option 7
Changing the valiation mode to onChange
will validate after change
event is triggered, when all of the values are up-to-date:
-- select up to 5 options --
select option 4
blur event fires from option 4
select option 5
change event fires from option 5 -> validate -> pass
blur event fires from option 5
select option 6
change event fires from option 6 -> validate -> fail
blur event fires from option 6
select option 7
change event fires from option 7 -> validate -> fail
Changing the valiation mode to all
will validate both in blur
and change
events, which may be overkill in this workflow, but it also works.
Upvotes: 50