我正在尝试验证一个具有动态字段数量的表单,即,从API返回数据,该API确定显示的行数,并且对于每一行都有一个必需的字段,需要用户选择一个输入以供他们前进。
要使用的包装是Yup和Formik。当查看Yup教程时,对象通常构建如下:
let userSchema = object({
name: string().required(),
});
其中定义了名称等键。然而,我的密钥需要是动态的,即字段1、字段2等,因为我不知道提前会有多少。
我想循环遍历我的对象,并将一组动态键传递给模式——基本上,无论对象有多长,都取决于我有多少键。
let userSchema = object({
[field1]: string().required(),
[field2]: string().required(),
});
然而,我不确定如何实现这一结果。我可以循环浏览我的对象,并尝试构建一组密钥,例如
let myObject = {}
myKeyObject.forEach((key) => myObject[key] = string().required());
然后将myKeyObject
传递给object.shape,但这通常会产生TS错误。有人知道Yup中动态形式的任何实用程序吗?除非我遗漏了什么,否则我在文档中看不到任何能让更容易处理动态表单的内容
如果你想要动态字段,你可以添加一个字段数组(包含字段或键的名称、标签、初始值和字段类型(,然后从该数组生成一个Schema,这里有一个例子:
import React, { Fragment } from 'react';
import { Field, Form, Formik } from 'formik';
import { string, object, number } from 'yup';
interface IField{
name: string,
label: string,
initialValue: any,
type: any
}
const fields: IField[] = [
{
name: 'firstName',
label: 'Firstname',
initialValue: '',
type: string().required()
},
{
name: 'lastName',
label: 'Lastname',
initialValue: '',
type: string().required()
},
{
name: 'email',
label: 'Email',
initialValue: '',
type: string().required()
},
{
name: 'password',
label: 'Password',
initialValue: '',
type: string().required()
},
{
name: 'age',
label: 'Age',
initialValue: 18,
type: number()
}
];
const initialValues = Object.fromEntries(fields.map((field)=>[field.name, field.initialValue]))
const SchemaObject = Object.fromEntries(fields.map((field)=>[field.name, field.type]))
const UserSchema = object().shape(SchemaObject);
const App = () => (
<Fragment>
<h1>User</h1>
<Formik
initialValues={initialValues}
onSubmit={values =>
console.log({values})
}
validationSchema={UserSchema}
>
{({ errors, touched }) => {
return(
<Form>
<div>
{fields.map(({label, name}, index) => (
<div key={index}>
<label style={{width: 100, display: 'inline-block'}}>{label}</label>
<Field name={name} />
{touched[name] && errors[name] && <div style={{color: 'red'}}>{errors[name]?.toString()}</div>}
</div>
))}
<div>
<button type="submit">Submit</button>
</div>
</div>
</Form>
);
}}
</Formik>
</Fragment>
);
export default App;
**Today i was working on too my much forms so i was trying to make it more dynamic**
**Do you mean like this**
**My Validation schema generator**
import testFormModel from './testFormModel';
import * as yup from 'yup';
const { formField } = testFormModel;
const [firstName] = formField;
const dynamicValidationGenerator = formField => {
//dynamic required validation for required field
const validateObj = {};
formField.map(field => {
field.required &&
Object.assign(validateObj, {
[field.name]: yup
.string()
.required(`${field.errorText.requiredErrorMsg}`),
});
});
return validateObj;
};
//for manual validation + dynamic validation
export default yup.object().shape({
...dynamicValidationGenerator(formField),
[firstName.name]: yup.string().min(5),
});
**my form model**
export default {
formId: 'testForm',
formField: [
{
name: 'firstName',
label: 'First Name',
required: true,
errorText: {
requiredErrorMsg: 'Required message',
},
},
{
name: 'lastName',
label: 'Last Name',
required: true,
errorText: {
requiredErrorMsg: 'Required message',
},
},
{ name: 'email', label: 'Email' },
{ name: 'age', label: 'Age' },
{ name: 'gender', label: 'Gender' },
],
};
**Initial form field value generator**
const initialFormValueGenerator = formField => {
const initialValues = {};
formField.map(el => Object.assign(initialValues, { [el.name]: '' }));
return initialValues;
};
export default initialFormValueGenerator;
**Input field**
import React from 'react';
import { useField } from 'formik';
function InputField(props) {
const { errorText, ...rest } = props;
const [field] = useField(props);
return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<label>{props.label}</label>
{props?.required && <span style={{ color: 'red' }}>*</span>}
<input
type='text'
onChange={value => console.log(value)}
name={props.name}
{...field}
{...rest}
/>
</div>
);
}
export default InputField;
**Form field html **
import React from 'react';
import InputField from '../FormField/InputField';
function AddressForm(props) {
const { formField } = props;
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 20,
padding: 20,
}}
>
{formField.map(field => {
return (
<div key={field.name}>
<InputField {...field} />
</div>
);
})}
</div>
);
}
export default AddressForm;
**App.js**
import { Formik, Form } from 'formik';
import React from 'react';
import AddressForm from './Features/Form/AddressForm';
import testFormModel from './Features/FormModel/testFormModel';
import validationSchema from './Features/FormModel/validationSchema';
import initialFormValueGenerator from './Features/Form/formInitialValues';
function App() {
const { formId, formField } = testFormModel;
const _handleSubmit = value => {
console.log('submitted', value);
};
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
width: '50%',
border: '1px solid black',
display: 'flex',
flexDirection: 'column',
marginTop: 20,
padding: 20,
backgroundColor: 'orange',
}}
>
<Formik
initialValues={initialFormValueGenerator(formField)}
validationSchema={validationSchema}
onSubmit={_handleSubmit}
>
{() => (
<Form id={formId}>
<AddressForm formField={formField} />
<div>
<button type='submit'>Submit</button>
</div>
</Form>
)}
</Formik>
</div>
</div>
);
}
export default App;
...
const FormValidation = Yup.object();
const DynamicForm = (props) => {
const [obj, setObj] = useState(props.editObj);
const validations = props.fields?.reduce((acc, curr) => {
acc[curr.name] = Yup.string().required("Required");
return acc;
}, {});
const formik = useFormik({
initialValues: obj || {},
onSubmit: props.onSubmit,
validationSchema: FormValidation.shape(validations),
enableReinitialize: true,
validateOnChange: false
});
}
...