如何使用 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}
        />
      )}
    />
  )}
/>