如何使用 React Hook Form 和 Yup 验证自动完成多个 TextField?
How can I validate an Autocomplete multiple TextField using React Hook Form and Yup?
我正在使用 Material UI 的自动完成多个 TextField、React Hook Form 和 Yup 来验证表单输入。
当我对 daysOfWeek 使用 Yup.string() 时,即使我选择了值,它也会显示错误消息。但是,如果我将其更改为Yup.array(),则会显示以下错误...
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()
有没有一种方法可以使用 Yup 来验证 Material UI 的自动完成多个 TextField?提前致谢!
这是我的相关代码...
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>
我做了一个异步、动态和 formik 自动完成。
这是我的表单文件:
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>
</>)
}
这是我的自动完成文件:
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>
),
}}
/>
)}
/>
);
}
我制作了一个小沙盒并更改了一些东西以使其工作:
- 你应该使用
<Controller />
而不是 register
作为 external controlled components 就像 MUI 的 <Autocomplete />
- 因为你必须为
<Controller/>
传递一个 defaultValue
(在你的例子中是一个空数组) - 然后你可以使用数组的 Yup.min
方法来测试用户是否有至少选择一周中的一天并摆脱 Yup.required
测试,因为它们在您的情况下所做的或多或少相同
- 你可以使用
<Autocomplete />
的 helperText
属性来显示错误信息
- 你可以将
ref
从 <Controller />
传递给 <TextField />
的 inputRef
属性,这样当你提交时没有选择星期几它会自动聚焦用户字段
const 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}
/>
)}
/>
)}
/>
我正在使用 Material UI 的自动完成多个 TextField、React Hook Form 和 Yup 来验证表单输入。
当我对 daysOfWeek 使用 Yup.string() 时,即使我选择了值,它也会显示错误消息。但是,如果我将其更改为Yup.array(),则会显示以下错误...
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()
有没有一种方法可以使用 Yup 来验证 Material UI 的自动完成多个 TextField?提前致谢!
这是我的相关代码...
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>
我做了一个异步、动态和 formik 自动完成。
这是我的表单文件:
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>
</>)
}
这是我的自动完成文件:
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>
),
}}
/>
)}
/>
);
}
我制作了一个小沙盒并更改了一些东西以使其工作:
- 你应该使用
<Controller />
而不是register
作为 external controlled components 就像 MUI 的<Autocomplete />
- 因为你必须为
<Controller/>
传递一个defaultValue
(在你的例子中是一个空数组) - 然后你可以使用数组的Yup.min
方法来测试用户是否有至少选择一周中的一天并摆脱Yup.required
测试,因为它们在您的情况下所做的或多或少相同 - 你可以使用
<Autocomplete />
的helperText
属性来显示错误信息 - 你可以将
ref
从<Controller />
传递给<TextField />
的inputRef
属性,这样当你提交时没有选择星期几它会自动聚焦用户字段
const 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}
/>
)}
/>
)}
/>