【问题标题】:How can I dynamically tie my form into Formik, in React functional component如何在 React 功能组件中动态地将我的表单绑定到 Formik
【发布时间】:2021-01-27 15:11:09
【问题描述】:

我正在构建一个 React 组件,它从外部 API 调用动态加载表单数据。用户完成后,我需要将其提交回另一个 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)?

【问题讨论】:

    标签: reactjs forms formik


    【解决方案1】:

    我遇到了同样的问题,我用 组件而不是 useFormik() 钩子解决了它。不知道为什么 useFormik 在动态验证上失败,但无论如何,在切换到 之后,下面是对我有用的代码。请检查以下代码sn-p之后的关键点。

    <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>
    )}
    

    关键点:

    1. 我的动态字段也来自远程 API,通过选择 categoryId DropDown,并设置为 requestCategoryFields 状态。
    2. 我将动态字段的值存储在一个名为 dynamicAttrsMap 的嵌套对象中,该对象位于我的主对象 request 中,因为我也有静态字段。
    3. 我使用了validate 道具,其中包括对categoryId 字段的一个静态验证,然后是一个为所有其他动态字段添加动态验证的循环(映射)。 此策略适用于每次状态更改,您的组件会重新渲染,并且您的验证是从头开始创建的。
    4. 我有两种验证方式,一种是检查必填字段的值是否存在。如果需要,另一个检查动态字段内容的正则表达式验证。
    5. 我将动态字段的渲染移至单独的组件&lt;DynamicField&gt; 以实现更好的模块化。请查看my DynamicField component
    6. 您可以将验证道具移动到const 以获得更好的编码;这里我的代码有点乱。
    7. 我使用了两个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 有时会很棘手。我们必须互相帮助。

    【讨论】:

    • 是的,即使许多开发人员的要求可能是使用 HOC withFormik,我发现更简单的 Formik 组件在不需要使用 formik 的情况下工作得更好。
    【解决方案2】:

    enableReinitialize添加到formik

    useFormik({
        initialValues: initialData,
        enableReinitialize: true,
        onSubmit: values => {
          // Do something
        }
    });
    

    FORMIK Doc

    【讨论】:

    • 重新初始化与问题完全无关。
    猜你喜欢
    • 2020-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-01
    相关资源
    最近更新 更多