I have react hook form Controller inside input, I want to add onBlur Yup validation for my input!!. I want to add validation in min field onBlur event validation 'min field should enter numbers only' using yup.
I have mention my code below.
My input code
<Controller
name="min"
control={control}
render={({ field: { onChange, value,ref } }) => (
<MaskedTextField
maskFormat={{
"*": /[0-9.,-]/,
}}
value={value}
onChange={onChange}
// {...register('min')}
inputRef={ref}
//onBlur={onBlurConstraints}
className="inputStyle"
/>
)}
/>
React hook-form
` const {
control,
handleSubmit,
watch,
setValue,
reset,
register,
formState: { errors },
} = useForm({
mode: "onBlur",
resolver: yupResolver(validationSchema),
});`
Related
React hook form - form is completely optional or completely mandatory (if any of the field is filled). Please help the optimized way. Thanks.
If I understood correctly, you want to submit the form with default/empty
values only if none of the fields have been modified, and otherwise all
fields need to have filled values?
The simplest approach I believe would be to use the isDirty prop of formState to set the
required prop in the register function.
const {
register,
handleSubmit,
formState: { errors, isDirty },
} = useForm();
return (
<form onSubmit={handleSubmit(() => {})}>
<TextField
label={'optional1'}
error={!!errors.optional1}
{...register('optional1', { required: isDirty })}
/>
<TextField
label={'optional2'}
error={!!errors.optional2}
{...register('optional2', { required: isDirty })}
/>
<Button color="inherit" type="submit">
Create
</Button>
</form>
);
This way however, e.g. text fields
remain dirty after emptying some added input. So you would need to add a some form reset functionality such as a reset-button to fix that.
Although, instead of a reset button, I would check that the field values equal default/empty values.
Note that the checking will require a bit more code if you have different kinds of fields or different default values. A reset button might be nice in this case also if you have a lot of fields though.
const {
register,
handleSubmit,
getValues,
formState: { errors },
} = useForm();
const fieldValues = Object.values(getValues());
const someOptionalFilled = fieldValues.some(fieldValue => fieldValue != '')
return (
<form onSubmit={handleSubmit(() => {})}>
<TextField
label={'optional1'}
error={!!errors.optional1}
{...register('optional1', { required: someOptionalFilled })}
/>
<TextField
label={'optional2'}
error={!!errors.optional2}
{...register('optional2', { required: someOptionalFilled })}
/>
<Button color="inherit" type="submit">
Create
</Button>
</form>
);
I want to create a custom validation starting from the validation below. But I'm not successful so far. I had visited this site and followed the codes in his "Custom validation rules" but I can't replicate it.
The isBefore method is working fine, but the validation does not. And also how can we put a custom message with this custom validation?
const isBefore = (date1, date2) => moment(date1).isBefore(moment(date2));
const rules = {
publishedDate: {
required: 'The published date is required.',
before: isBefore(scheduledDate, expiredDate)
},
}
<Controller
control={control}
name="publishedDate"
rules={rules.publishedDate}
render={({ onChange }) => (
<DatePicker
className="mb-px-8"
onChange={(value) => {
setPublishedDate(value);
onChange(value);
}}
minDate={new Date()}
value={publishedDate}
/>
)}
/>
Here is my attempt:
you need to use the hook useEffect and a controller.
at the top of the page you need these two imports:
import React, { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
then you need the validation function this lives outside of the component.
const isBefore = (date) => {
if (!date) {
return false;
}
const today = new Date();
today.setHours(0, 0, 0, 0);
return date > today;
};
The above function checks that the date you picked is in the future and not the past.
Under your component you set everything to useForm
const {
register,
handleSubmit,
control,
setValue,
watch,
errors,
setError,
clearError
} = useForm();
You then setup variables to watch for the datepicker to update, along with useEffect to watch for the change:
const startDate = watch("startDate");
useEffect(() => {
register({ name: "startDate", type: "custom" }, { validate: { isBefore } });
});
You then define a handler inside of the component that handles the data change along with the validation.
const handleDateChange = (dateType) => (date) => {
if (!isBefore(date)) {
setError(dateType, "isBefore");
} else {
setError(dateType, "isBefore");
}
setValue(dateType, date);
alert(date);
};
the custom error message can exist anywhere in the form and you don't need to tie a ref to it. the useForm() and watch('startDate') control the data for you.
Here is the custom error message that can live anywhere within the form component.
Please see updated codesandbox where I have the custom error message displayed near the submit button
{errors.startDate && (
<div variant="danger">
{errors.startDate.type === "isBefore" && (
<p>Please choose present or future date!</p>
)}
</div>
Here is a working codesandbox that I cleaned up a bit from yesterday, and added in some comments.
https://codesandbox.io/s/play-momentjs-forked-1hu4s?file=/src/index.js:1494-1802
If you click the input and then choose a date in the past, and then click submit, the custom error message will show. However, if you select a date in the future and hit submit the message doesn't show.
Here is a resource I used:
https://eincode.com/blogs/learn-how-to-validate-custom-input-components-with-react-hook-form
Also more information on watch that you get from useForm function:
https://react-hook-form.com/api/useform/watch/
Try using rules of react-hook-form to add validations
<Controller
name="currentName"
control={control}
render={({ field }) => (
<TextField
value={field.value}
onChange={field.onChange}
inputRef={field.ref}
variant="outlined"
size="small"
fullWidth
autoComplete="off"
helperText={helperText}
/>
)}
rules={{
validate: {
required: (value) => {
if (value === "SomeValue") return 'Some Message';
if (!value) return '*Required';
}
},
maxLength: 5
}}
defaultValue=""
/>
I took the example from the documentation :
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
console.log(watch("example"));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input defaultValue="test" {...register("example")} />
<input type="submit" />
</form>
);
}
But on every change or on submit, I got undefined for each field
I tried to install the library again but nothing change and I got undefined everywhere...seems to be a problem with the register function. Does anybody got the same issue ?
With v7 the usage of register changed as noted in the comments. If you still need to use v6, you have to write it like this:
function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
console.log(watch("example"));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input defaultValue="test" name="example" ref={register} />
<input type="submit" />
</form>
);
}
Docs v6
In my case it was a typo:
<input defaultValue="test" {...(register('name'), { required: true })} />
// submit => { name: undefined }
Instead of:
<input defaultValue="test" {...(register('name', { required: true }))} />
// submit => { name: "test" }
Hopefully it can help someone else.
In my case, I was using a Controller, so to fix the Undefined value I just had to pass defaultValues to useForm.
See the rules section here: https://react-hook-form.com/api/useform/watch
const { register, handleSubmit, control, setValue} = useForm<MyFormValues>({
defaultValues : {
receiveUpdates: false
}
});
<Controller
control={control}
name="receiveUpdates"
render={({ field }) => (
<FormControlLabel
control={
<Checkbox
ref={field.ref}
checked={field.value}
onChange={field.onChange}
/>
}
label="Receive Email Updates?"
labelPlacement="start"
/>
)}
/>
I had this issue when using the Input component from reactstrap. Using that component made all my values undefined. I switch the Input to a normal input and was able to read in values
Before:
<Input
placeholder="Password"
type="password"
id="password"
defaultValue=""
{...register('password')}
required
/>
Fixed:
<input
placeholder="Password"
type="password"
id="password"
defaultValue=""
{...register('password')}
required
/>
In my case I installed like "npm i react-hook-form" and I don't know why, but it was installed ^6.15.8 version, and I removed it and try again and then it was install correctly. So try to check out your version of react-hook-form
I want to create a custom input with new register method by passing into props of react-hook-form, I have the same message everytime:
TypeError: path.split is not a function
https://gyazo.com/414ea28dbe2b016e5b0739660efdc84b
My custom input
function Field({
name,
register,
placeholder,
type,
value,
onChange,
defaultValue,
errors,
children
}){
return(
<Form.Group>
<Form.Label htmlFor={name}>{children}</Form.Label>
<Form.Control
size="lg"
placeholder={placeholder}
type={type ? type : "text"}
id={name}
// name={name}
value={value}
onChange={onChange}
{...register({name})}
defaultValue={defaultValue}
/>
{errors && <span className="text-danger">{errors.message}</span>}
</Form.Group>
);
}
And my form part where I call my custom input:
const {register, formState : { errors }, handleSubmit} = useForm({
mode: "onTouched",
resolver: yupResolver(schema),
});
...
<Field
name="login"
register={register}
errors={errors.login}
>
...
From the migration guide to v7:
Custom register
You will no longer need the name attribute for custom
register, you can supply the name of the input straight way.
- register({ name: 'test' })
+ register('test')
It looks like react-hook-form is trying to split the name string in case you have a nested field:
'test.0.nested'.split('.')
But with the new version you should be supply a string instead of an object with a name property so:
{ name: 'test.0.nested' }.split('.')
Throws the error.
I am trying to build a form that accommodates multiple 'grouped' checkboxes using react-form-hook Material UI.
The checkboxes are created async from an HTTP Request.
I want to provide an array of the objects IDs as the default values:
defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] }
Also, when I select or deselect a checkbox, I want to add/remove the ID of the object to the values of react-hook-form.
ie. (boat_ids: [25, 29, 4])
How can I achieve that?
Here is a sample that I am trying to reproduce the issue.
Bonus point, validation of minimum selected checkboxes using Yup
boat_ids: Yup.array() .min(2, "")
I've been struggling with this as well, here is what worked for me.
Updated solution for react-hook-form v6, it can also be done without useState(sandbox link below):
import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const defaultNames = ["bill", "Manos"];
const { control, handleSubmit } = useForm({
defaultValues: { names: defaultNames }
});
const [checkedValues, setCheckedValues] = useState(defaultNames);
function handleSelect(checkedName) {
const newNames = checkedValues?.includes(checkedName)
? checkedValues?.filter(name => name !== checkedName)
: [...(checkedValues ?? []), checkedName];
setCheckedValues(newNames);
return newNames;
}
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
{["bill", "luo", "Manos", "user120242"].map(name => (
<FormControlLabel
control={
<Controller
name="names"
render={({ onChange: onCheckChange }) => {
return (
<Checkbox
checked={checkedValues.includes(name)}
onChange={() => onCheckChange(handleSelect(name))}
/>
);
}}
control={control}
/>
}
key={name}
label={name}
/>
))}
<button>Submit</button>
</form>
);
}
Codesandbox link: https://codesandbox.io/s/material-demo-54nvi?file=/demo.js
Another solution with default selected items done without useState:
https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js
Breaking API changes made in 6.X:
validation option has been changed to use a resolver function wrapper and a different configuration property name
Note: Docs were just fixed for validationResolver->resolver, and code examples for validation in repo haven't been updated yet (still uses validationSchema for tests). It feels as if they aren't sure what they want to do with the code there, and it is in a state of limbo. I would avoid their Controller entirely until it settles down, or use Controller as a thin wrapper for your own form Controller HOC, which appears to be the direction they want to go in.
see official sandbox demo and the unexpected behavior of "false" value as a string of the Checkbox for reference
import { yupResolver } from "#hookform/resolvers";
const { register, handleSubmit, control, getValues, setValue } = useForm({
resolver: yupResolver(schema),
defaultValues: Object.fromEntries(
boats.map((boat, i) => [
`boat_ids[${i}]`,
preselectedBoats.some(p => p.id === boats[i].id)
])
)
});
Controller no longer handles Checkbox natively (type="checkbox"), or to better put it, handles values incorrectly. It does not detect boolean values for checkboxes, and tries to cast it to a string value. You have a few choices:
Don't use Controller. Use uncontrolled inputs
Use the new render prop to use a custom render function for your Checkbox and add a setValue hook
Use Controller like a form controller HOC and control all the inputs manually
Examples avoiding the use of Controller:
https://codesandbox.io/s/optimistic-paper-h39lq
https://codesandbox.io/s/silent-mountain-wdiov
Same as first original example but using yupResolver wrapper
Description for 5.X:
Here is a simplified example that doesn't require Controller. Uncontrolled is the recommendation in the docs. It is still recommended that you give each input its own name and transform/filter on the data to remove unchecked values, such as with yup and validatorSchema in the latter example, but for the purpose of your example, using the same name causes the values to be added to an array that fits your requirements.
https://codesandbox.io/s/practical-dijkstra-f1yox
Anyways, the problem is that your defaultValues doesn't match the structure of your checkboxes. It should be {[name]: boolean}, where names as generated is the literal string boat_ids[${boat.id}], until it passes through the uncontrolled form inputs which bunch up the values into one array. eg: form_input1[0] form_input1[1] emits form_input1 == [value1, value2]
https://codesandbox.io/s/determined-paper-qb0lf
Builds defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true ... }
Controller expects boolean values for toggling checkbox values and as the default values it will feed to the checkboxes.
const { register, handleSubmit, control, getValues, setValue } = useForm({
validationSchema: schema,
defaultValues: Object.fromEntries(
preselectedBoats.map(boat => [`boat_ids[${boat.id}]`, true])
)
});
Schema used for the validationSchema, that verifies there are at least 2 chosen as well as transforms the data to the desired schema before sending it to onSubmit. It filters out false values, so you get an array of string ids:
const schema = Yup.object().shape({
boat_ids: Yup.array()
.transform(function(o, obj) {
return Object.keys(obj).filter(k => obj[k]);
})
.min(2, "")
});
Here is a working version:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const { control, handleSubmit } = useForm({
defaultValues: {
bill: "bill",
luo: ""
}
});
return (
<form onSubmit={handleSubmit(e => console.log(e))}>
{["bill", "luo"].map(name => (
<Controller
key={name}
name={name}
as={
<FormControlLabel
control={<Checkbox value={name} />}
label={name}
/>
}
valueName="checked"
type="checkbox"
onChange={([e]) => {
return e.target.checked ? e.target.value : "";
}}
control={control}
/>
))}
<button>Submit</button>
</form>
);
}
codesandbox link: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932
However, I do not recommend doing so, because Checkbox in material UI probably should return checked (boolean) instead of (value).
Here's my solution, which is not using all the default components from Material UI cause at my interface each radio will have an icon and text, besides the default bullet point not be showed:
const COMPANY = "company";
const INDIVIDUAL = "individual";
const [scope, setScope] = useState(context.scope || COMPANY);
const handleChange = (event) => {
event.preventDefault();
setScope(event.target.value);
};
<Controller
as={
<FormControl component="fieldset">
<RadioGroup
aria-label="scope"
name="scope"
value={scope}
onChange={handleChange}
>
<FormLabel>
{/* Icon from MUI */}
<Business />
<Radio value={COMPANY} />
<Typography variant="body1">Company</Typography>
</FormLabel>
<FormLabel>
{/* Icon from MUI */}
<Personal />
<Radio value={INDIVIDUAL} />
<Typography variant="body1">Individual</Typography>
</FormLabel>
</RadioGroup>
</FormControl>
}
name="scope"
control={methods.control}
/>;
Observation: At this example I use React Hook Form without destruct:
const methods = useForm({...})
This is my solution with react hook form 7, the other solutions don't work with reset or setValue.
<Controller
name={"test"}
control={control}
render={({ field }) => (
<FormControl>
<FormLabel id={"test"}>{"label"}</FormLabel>
<FormGroup>
{items.map((item, index) => {
const value = Object.values(item);
return (
<FormControlLabel
key={index}
control={
<Checkbox
checked={field.value.includes(value[0])}
onChange={() =>
field.onChange(handleSelect(value[0],field.value))
}
size="small"
/>
}
label={value[1]}
/>
);
})}
</FormGroup>
</FormControl>
)}
/>
link to codesandbox: Mui multiple checkbox