'Yup / Formik validation using dynamic keys

I am trying to validate a form with a dynamic amount of fields - i.e., data is returned from an API that determines how many rows are shown, and for each row there is a required field that needs the user to select an input for them to advance.

The package to be used is Yup alongside Formik. When looking on Yup tutorials, the object is usually built as follows:

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

Where keys like name are defined. However, my keys will need to be dynamic i.e. field1, field2 etc. as I do not know ahead of time how many of them there will be.

I want to loop through my object and pass a dynamic set of keys to the schema - basically however long the object is, will be how many keys I have.

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

However, I am unsure of how to achieve this result. I can loop through my object and try to build a set of keys, rough e.g.

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

And then pass myKeyObject to object.shape, but this usually produces TS errors. Does anyone know of any utility within Yup for a dynamic form? Unless there is something I have missed, I don't see anything on the documentation that would make working with dynamic forms easier



Solution 1:[1]

If you want dynamic fields you can add an array of fields (containing the name of field or key, label, initial value and the type of field) then generate a Schema from that array, here is an example:

import React, { Fragment } from 'react';
import { Field, Form, Formik } from 'formik';
import { string, object, number } from 'yup';

interface Fields{
  name: string,
  label: string,
  initialValue: any,
  type: any
}

const fields: Fields[] = [
  {
    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;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1