Reputation: 85
I am using Material UI's Autocomplete multiple TextField, React Hook Form, and Yup to validate the form inputs.
When I use Yup.string() for the daysOfWeek, even if I have values selected, it shows the error message. However, if I change it to Yup.array(), the following error is displayed...
daysOfWeek must be a
array
type, but the final value was:null
(cast from the value""
). If "null" is intended as an empty value be sure to mark the schema as.nullable()
Is there a way I can use Yup to validate Material UI's Autocomplete multiple TextField? Thank you in advance!
This is my relevant code...
const [selected, setSelected] = useState([]);
const validationSchema = Yup.object().shape({
daysOfWeek: Yup.string()
.required("Days of the week are required")
});
const {
formState: {errors},
handleSubmit,
register
} = useForm({
resolver: yupResolver(validationSchema)
});
<Autocomplete
disableClearable
disablePortal
filterSelectedOptions
multiple
getOptionDisabled={(option) => option.disabled ? true : false}
getOptionLabel={(option) => option.label}
id="days-autocomplete"
onChange={(event, value) => onOptionsChange(event, value)}
options={daysOfWeekSuggestions}
renderInput={(params) => <TextField
required
error={errors.daysOfWeek ? true : false}
id="daysOfWeek"
label="Days of the week"
name="daysOfWeek"
type="search"
{...params}
{...register("daysOfWeek")}
/>}
value={selected}
/>
<Typography color="error" sx={errorSx} variant="inherit">{errors.daysOfWeek?.message}</Typography>
Upvotes: 3
Views: 11837
Reputation: 6949
I made a small Sandbox and changed a few things to make it work:
<Controller />
instead of register
for external controlled components like MUI's <Autocomplete />
defaultValue
for <Controller/>
(in your case an empty array) - you can then use the Yup.min
method for arrays to test if the user has at least selected one day of the week and get rid of the Yup.required
test as they both do more or less the same in your situationhelperText
prop of <Autocomplete />
to show the error messageref
from <Controller />
to the inputRef
prop of <TextField />
so that when you submit without selecting a day of the week it will automatically focus the field for the userconst validationSchema = Yup.object().shape({
daysOfWeek: Yup.array()
.of(
Yup.object().shape({
value: Yup.string(),
label: Yup.string()
})
)
.min(1, "Days of the week are required")
});
<Controller
name="daysOfWeek"
control={control}
defaultValue={[]}
render={({ field: { ref, ...field }, fieldState: { error } }) => (
<Autocomplete
{...field}
disableClearable
disablePortal
filterSelectedOptions
multiple
getOptionDisabled={(option) => option.disabled}
getOptionLabel={(option) => option.label}
id="days-autocomplete"
onChange={(event, value) => field.onChange(value)}
options={daysOfWeekSuggestions}
renderInput={(params) => (
<TextField
required
error={!!error}
helperText={error?.message}
id="daysOfWeek"
label="Days of the week"
name="daysOfWeek"
type="search"
inputRef={ref}
{...params}
/>
)}
/>
)}
/>
Upvotes: 6
Reputation: 2733
I made an async ,dynamicly and formik autocomplete.
this is my form file:
const FormSimple = (props) => {
const validationSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
nationalId: Yup.number("Invalid Number")
.min(2, 'Too Short!')
.max(9999999999, 'Too Long!')
.required('Required'),
correspEmail: Yup.string().email('Invalid email'),
financeNationalNo: Yup.number(),
});
const formik = useFormik({
initialValues: details,
validationSchema: validationSchema,
onSubmit: (values) => {
if (id !== "new")
editData(values)
else
sendData(values)
},
});
return (<>
<form onSubmit={formik.handleSubmit}>
<div className="row">
<InputSize>
<Asynchronous getListData={getListCountry} onChange={(value)=> {
formik.setFieldValue("registrationCountryName",value.nameEn)
formik.setFieldValue("registrationCountryId",value.id)
}} name="nameEn"
defaultValue={formik.values?.registrationCountryName ? {
nameEn: formik.values?.registrationCountryName,
id: formik.values?.registrationCountryId
} : null}
value={formik.values?.registrationCountryName ? {
nameEn: formik.values?.registrationCountryName,
id: formik.values?.registrationCountryId
} : null}
required={true}
showName="Country Of Registration" label="nameEn" id="id"/>
</InputSize>
<div className="col-12 mt-4">
<Button variant="contained" color="primary" type="submit"
disabled={loading}>Submit</Button>
</div>
</div>
</form>
</>)
}
and this is my autocomplete file :
import * as React from 'react';
import TextField from '@mui/material/TextField';
import Autocomplete from '@mui/material/Autocomplete';
import CircularProgress from '@mui/material/CircularProgress';
import {useRef} from "react";
export default function Asynchronous(props) {
const {onChange, name, getListData, label = "name", id = "id", showName,defaultValue,disabled,required,value,noOption="No Option"} = props;
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const [filters, setFilters] = React.useState(null);
const [loadingApi, setLoadingApi] = React.useState(false)
const loading = open && options.length === 0;
let timeOut = useRef(null);
const getData = async (search = "") => {
setLoadingApi(true)
const data = await getListData(search); // For demo purposes.
// console.log(data)
setLoadingApi(false);
// console.log(data)
if(data)
setOptions([...data]);
}
React.useEffect(() => {
if (!loading) {
return undefined;
}
getData();
}, [loading]);
React.useEffect(() => {
if (filters !== null) {
if (timeOut.current !== null)
clearTimeout(timeOut.current);
timeOut.current = setTimeout(() => getData(filters), 500);
}
}, [filters]);
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<Autocomplete
disabled={disabled}
id={name}
name={name}
sx={{width: "100%"}}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
defaultValue={defaultValue}
value={value}
isOptionEqualToValue={(option, value) => option?.[id] === value?.[id]}
getOptionLabel={(option) => option?.[label]}
options={options}
onChange={(e, value) => onChange(value)}
loading={loadingApi}
noOptionsText={noOption}
renderInput={(params) => (
<TextField
name={name}
required={required}
variant="standard"
{...params}
label={showName}
onChange={(e) => setFilters(e.target.value)}
InputProps={{
...params.InputProps,
onblur:() => {},
endAdornment: (
<React.Fragment>
{loadingApi ? <CircularProgress color="inherit" size={20}/> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
}
Upvotes: 2