網站搜尋

reactjs - 使用動態鍵的 Yup/Formik 驗證


我正在嘗試驗證一個具有動態欄位數量的表單——也就是說,數據是從一個 API 返回的,該 API 決定顯示多少行,每一行都有一個必填欄位,使用者必須選擇一個輸入才能繼續。

將要使用的套件是 Yup 搭配 Formik。在查看 Yup 教學時,物件通常是這樣建立的:

let userSchema = object({
  name: string().required(),
});

像 name 這樣的鍵是被定義好的。然而,我的鍵需要是動態的,也就是 field1、field2 等,因為我事先不知道會有多少個這樣的鍵。

我想要遍歷我的物件,並將一組動態的鍵傳遞給 schema——基本上物件的長度就是我將擁有的鍵的數量。

let userSchema = object({
  [field1]: string().required(),
  [field2]: string().required(),
});

然而,我不確定如何達到這個結果。我可以遍歷我的物件並嘗試建立一組鍵,例如粗略地說。

let myObject = {}
myKeyObject.forEach((key) => myObject[key] = string().required());

然後將 myKeyObject 傳給 object.shape,但這通常會產生 TS 錯誤。有人知道 Yup 中有任何用於動態表單的工具嗎?除非我漏掉了什麼,否則我在文件中看不到任何可以讓動態表單更容易使用的東西

答案#1

如果您想要動態欄位,您可以添加一個欄位陣列(包含欄位名稱或鍵、標籤、初始值和欄位類型),然後從該陣列生成一個 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;