在 React 功能组件中,如何将我的表单动态绑定到 Formik
How can I dynamically tie my form into Formik, in React functional component
我正在构建一个从外部 API 调用动态加载表单数据的 React 组件。我需要在用户完成后将其提交回另一个 API 端点。
这里是:
import React, { useEffect, useState } from "react";
import axios from "axios";
import TextField from "@material-ui/core/TextField";
import { useFormik } from "formik";
const FORM = () => {
const [form, setForm] = useState([]);
const [submission, setSubmission] = useState({});
const formik = useFormik({
initialValues: {
email: "",
},
});
useEffect(() => {
(async () => {
const formData = await axios.get("https://apicall.com/fakeendpoint");
setForm(formData.data);
})();
}, []);
return (
<form>
{form.map((value, index) => {
if (value.fieldType === "text") {
return (
<TextField
key={index}
id={value.name}
label={value.label}
/>
);
}
if (value.fieldType === "select") {
return (
<TextField
select={true}
key={index}
id={value.name}
label={value.label}
>
{value.options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
);
}
})}
<button type="submit">Submit</button>
</form>
);
};
export default FORM;
API 调用正常(是的,我现在需要一些错误处理)并且我能够填充字段并在页面上获取表单。我遇到麻烦的地方(我是 Formik 的新手,所以请耐心等待)是我需要进行验证和提交。我真的不知道如何处理这些值,通常我会为变量编写某种类型的静态代码,测试它们然后提交。
通常 例如,我会为“姓名”设置一个字段,为“电子邮件”设置一个字段。在这种情况下,我无法写入这些字段,因为它们来自 API 并且在我们收到呼叫响应之前我们不知道它们是什么。
我的代码处理字段创建,但我需要连接以进行验证和提交,并且想使用 Formik。
我如何完成连接验证和提交的动态表单(通过 formik)?
将 enableReinitialize
添加到 formik
useFormik({
initialValues: initialData,
enableReinitialize: true,
onSubmit: values => {
// Do something
}
});
我有同样的问题,我用 组件而不是 useFormik() 钩子解决了它。不确定为什么 useFormik 在动态验证时失败,但无论如何,在切换到 之后,下面是对我有用的代码。请同时检查以下代码片段后的要点。
<Formik
innerRef={formikRef}
initialValues={request || emptyRequest //Mostafa: edit or new: if request state is avaialble then use it otherwise use emptyRequest.
}
enableReinitialize="true"
onSubmit={(values) => {
saveRequest(values);
}}
validate={(data) => {
let errors = {};
if (!data.categoryId) {
errors.categoryId = 'Request Category is required.';
}
//Mostafa: Dynamic validation => as the component is re-rendered, the validation is set again after request category is changed. React is interesting and a lot of repetitive work is done.
if (requestCategoryFields)
requestCategoryFields.map(rcField => {
if (rcField.fieldMandatory) { //1- check Mandatory
if (!data.dynamicAttrsMap[rcField.fieldPropertyName]) {
if (!errors.dynamicAttrsMap) //Mostafa: Need to initialize the object in errors, otherwise Formik will not work properly.
errors.dynamicAttrsMap = {}
errors.dynamicAttrsMap[rcField.fieldPropertyName] = rcField.fieldLabel + ' is required.';
}
}
if (rcField.validationRegex) { //2- check Regex
if (data.dynamicAttrsMap[rcField.fieldPropertyName]) {
var regex = new RegExp(rcField.validationRegex); //Using RegExp object for dynamic regex patterns.
if (!regex.test(data.dynamicAttrsMap[rcField.fieldPropertyName])) {
if (!errors.dynamicAttrsMap) //Mostafa: Need to initialize the object in errors, otherwise Formik will not work properly.
errors.dynamicAttrsMap = {}
if (errors.dynamicAttrsMap[rcField.fieldPropertyName]) //add an Line Feed if another errors line already exists, just for a readable multi-line message.
errors.dynamicAttrsMap[rcField.fieldPropertyName] += '\n';
errors.dynamicAttrsMap[rcField.fieldPropertyName] = rcField.validationFailedMessage; //Regex validaiton error and help is supposed to be already provided in Field defintion by admin user.
}
}
}
});
return errors;
}}>
{({errors,handleBlur,handleChange,handleSubmit,resetForm,setFieldValue,isSubmitting,isValid,dirty,touched,values
}) => (
<form autoComplete="off" noValidate onSubmit={handleSubmit} className="card">
<div className="grid p-fluid mt-2">
<div className="field col-12 lg:col-6 md:col-6">
<span className="p-float-label p-input-icon-right">
<Dropdown name="categoryId" id="categoryId" value={values.categoryId} onChange={e => { handleRequestCategoryChange(e, handleChange, setFieldValue) }}
filter showClear filterBy="name"
className={classNames({ 'p-invalid': isFormFieldValid(touched, errors, 'categoryId') })}
itemTemplate={requestCategoryItemTemplate} valueTemplate={selectedRequestCategoryValueTemplate}
options={requestCategories} optionLabel="name" optionValue="id" />
<label htmlFor="categoryId" className={classNames({ 'p-error': isFormFieldValid(touched, errors, 'categoryId') })}>Request Category*</label>
</span>
{getFormErrorMessage(touched, errors, 'siteId')}
</div>
{
//Here goes the dynamic fields
requestCategoryFields && requestCategoryFields.map(rcField => {
return (
<DynamicField field={rcField} values={values} touched={touched} errors={errors} handleBlur={handleBlur}
handleChange={handleChange} isFormFieldValid={isFormFieldValid} getFormErrorMessage={getFormErrorMessage}
crudService={qaCrudService} toast={toast} />
)
})
}
</div>
<div className="p-dialog-footer">
<Button type="button" label="Cancel" icon="pi pi-times" className="p-button p-component p-button-text"
onClick={() => {
resetForm();
hideDialog();
}} />
<Button type="submit" label="Save" icon="pi pi-check" className="p-button p-component p-button-text" />
</div>
</form>
)}
要点:
- 我的动态字段也来自远程 API,通过选择类别 ID
DropDown
,并设置为 requestCategoryFields
状态。
- 我将动态字段的值存储在一个名为
dynamicAttrsMap
的嵌套对象中,该对象位于我的主对象内,该对象称为 request
,因为我也有静态字段。
- 我使用了
validate
道具,其中包括对 categoryId
字段的一个静态验证,然后是一个为所有其他动态字段添加动态验证的循环(映射)。 此策略适用于每次状态更改,您的组件会重新呈现,并且您的验证会从头开始创建。
- 我有两种验证,一种检查必填字段的值是否存在。如果需要,另一个检查动态字段内容的正则表达式验证。
- 为了更好的模块化,我将动态字段的渲染移到了一个单独的组件中
<DynamicField>
。请检查 my DynamicField component
- 您可以将验证道具移动到
const
以便更好地编码;这里我的代码有点乱。
- 我使用了两个
const
,用于更好的编码,用于错误消息和 CSS,如下所示:
const isFormFieldValid = (touched, errors, name) => { return Boolean(getIn(touched, name) && getIn(errors, name)) };
const getFormErrorMessage = (touched, errors, name) => {
return isFormFieldValid(touched, errors, name) && <small className="p-error">{getIn(errors, name)}</small>;
};
请在 my GitHub Here 查看我的完整组件,以便更好地理解。如果需要,请发表评论以获得任何澄清。因为我知道 Formik 有时会很棘手。我们必须互相帮助。
我正在构建一个从外部 API 调用动态加载表单数据的 React 组件。我需要在用户完成后将其提交回另一个 API 端点。
这里是:
import React, { useEffect, useState } from "react";
import axios from "axios";
import TextField from "@material-ui/core/TextField";
import { useFormik } from "formik";
const FORM = () => {
const [form, setForm] = useState([]);
const [submission, setSubmission] = useState({});
const formik = useFormik({
initialValues: {
email: "",
},
});
useEffect(() => {
(async () => {
const formData = await axios.get("https://apicall.com/fakeendpoint");
setForm(formData.data);
})();
}, []);
return (
<form>
{form.map((value, index) => {
if (value.fieldType === "text") {
return (
<TextField
key={index}
id={value.name}
label={value.label}
/>
);
}
if (value.fieldType === "select") {
return (
<TextField
select={true}
key={index}
id={value.name}
label={value.label}
>
{value.options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
);
}
})}
<button type="submit">Submit</button>
</form>
);
};
export default FORM;
API 调用正常(是的,我现在需要一些错误处理)并且我能够填充字段并在页面上获取表单。我遇到麻烦的地方(我是 Formik 的新手,所以请耐心等待)是我需要进行验证和提交。我真的不知道如何处理这些值,通常我会为变量编写某种类型的静态代码,测试它们然后提交。
通常 例如,我会为“姓名”设置一个字段,为“电子邮件”设置一个字段。在这种情况下,我无法写入这些字段,因为它们来自 API 并且在我们收到呼叫响应之前我们不知道它们是什么。
我的代码处理字段创建,但我需要连接以进行验证和提交,并且想使用 Formik。
我如何完成连接验证和提交的动态表单(通过 formik)?
将 enableReinitialize
添加到 formik
useFormik({
initialValues: initialData,
enableReinitialize: true,
onSubmit: values => {
// Do something
}
});
我有同样的问题,我用
<Formik
innerRef={formikRef}
initialValues={request || emptyRequest //Mostafa: edit or new: if request state is avaialble then use it otherwise use emptyRequest.
}
enableReinitialize="true"
onSubmit={(values) => {
saveRequest(values);
}}
validate={(data) => {
let errors = {};
if (!data.categoryId) {
errors.categoryId = 'Request Category is required.';
}
//Mostafa: Dynamic validation => as the component is re-rendered, the validation is set again after request category is changed. React is interesting and a lot of repetitive work is done.
if (requestCategoryFields)
requestCategoryFields.map(rcField => {
if (rcField.fieldMandatory) { //1- check Mandatory
if (!data.dynamicAttrsMap[rcField.fieldPropertyName]) {
if (!errors.dynamicAttrsMap) //Mostafa: Need to initialize the object in errors, otherwise Formik will not work properly.
errors.dynamicAttrsMap = {}
errors.dynamicAttrsMap[rcField.fieldPropertyName] = rcField.fieldLabel + ' is required.';
}
}
if (rcField.validationRegex) { //2- check Regex
if (data.dynamicAttrsMap[rcField.fieldPropertyName]) {
var regex = new RegExp(rcField.validationRegex); //Using RegExp object for dynamic regex patterns.
if (!regex.test(data.dynamicAttrsMap[rcField.fieldPropertyName])) {
if (!errors.dynamicAttrsMap) //Mostafa: Need to initialize the object in errors, otherwise Formik will not work properly.
errors.dynamicAttrsMap = {}
if (errors.dynamicAttrsMap[rcField.fieldPropertyName]) //add an Line Feed if another errors line already exists, just for a readable multi-line message.
errors.dynamicAttrsMap[rcField.fieldPropertyName] += '\n';
errors.dynamicAttrsMap[rcField.fieldPropertyName] = rcField.validationFailedMessage; //Regex validaiton error and help is supposed to be already provided in Field defintion by admin user.
}
}
}
});
return errors;
}}>
{({errors,handleBlur,handleChange,handleSubmit,resetForm,setFieldValue,isSubmitting,isValid,dirty,touched,values
}) => (
<form autoComplete="off" noValidate onSubmit={handleSubmit} className="card">
<div className="grid p-fluid mt-2">
<div className="field col-12 lg:col-6 md:col-6">
<span className="p-float-label p-input-icon-right">
<Dropdown name="categoryId" id="categoryId" value={values.categoryId} onChange={e => { handleRequestCategoryChange(e, handleChange, setFieldValue) }}
filter showClear filterBy="name"
className={classNames({ 'p-invalid': isFormFieldValid(touched, errors, 'categoryId') })}
itemTemplate={requestCategoryItemTemplate} valueTemplate={selectedRequestCategoryValueTemplate}
options={requestCategories} optionLabel="name" optionValue="id" />
<label htmlFor="categoryId" className={classNames({ 'p-error': isFormFieldValid(touched, errors, 'categoryId') })}>Request Category*</label>
</span>
{getFormErrorMessage(touched, errors, 'siteId')}
</div>
{
//Here goes the dynamic fields
requestCategoryFields && requestCategoryFields.map(rcField => {
return (
<DynamicField field={rcField} values={values} touched={touched} errors={errors} handleBlur={handleBlur}
handleChange={handleChange} isFormFieldValid={isFormFieldValid} getFormErrorMessage={getFormErrorMessage}
crudService={qaCrudService} toast={toast} />
)
})
}
</div>
<div className="p-dialog-footer">
<Button type="button" label="Cancel" icon="pi pi-times" className="p-button p-component p-button-text"
onClick={() => {
resetForm();
hideDialog();
}} />
<Button type="submit" label="Save" icon="pi pi-check" className="p-button p-component p-button-text" />
</div>
</form>
)}
要点:
- 我的动态字段也来自远程 API,通过选择类别 ID
DropDown
,并设置为requestCategoryFields
状态。 - 我将动态字段的值存储在一个名为
dynamicAttrsMap
的嵌套对象中,该对象位于我的主对象内,该对象称为request
,因为我也有静态字段。 - 我使用了
validate
道具,其中包括对categoryId
字段的一个静态验证,然后是一个为所有其他动态字段添加动态验证的循环(映射)。 此策略适用于每次状态更改,您的组件会重新呈现,并且您的验证会从头开始创建。 - 我有两种验证,一种检查必填字段的值是否存在。如果需要,另一个检查动态字段内容的正则表达式验证。
- 为了更好的模块化,我将动态字段的渲染移到了一个单独的组件中
<DynamicField>
。请检查 my DynamicField component - 您可以将验证道具移动到
const
以便更好地编码;这里我的代码有点乱。 - 我使用了两个
const
,用于更好的编码,用于错误消息和 CSS,如下所示:
const isFormFieldValid = (touched, errors, name) => { return Boolean(getIn(touched, name) && getIn(errors, name)) }; const getFormErrorMessage = (touched, errors, name) => { return isFormFieldValid(touched, errors, name) && <small className="p-error">{getIn(errors, name)}</small>; };
请在 my GitHub Here 查看我的完整组件,以便更好地理解。如果需要,请发表评论以获得任何澄清。因为我知道 Formik 有时会很棘手。我们必须互相帮助。