Dynamic forms with Formik and React JS. 📝
Tags: react, javascript, tutorial, webdev
Published: 2022-10-28 10:00:00

This time I will show you how to create dynamic forms with Formik and Yup, using React JS with TypeScript.

Any kind of **feedback** is welcome, thanks and I hope you enjoy the article.🤗

>**⚠️ Note**: You need to have basic knowledge in React JS and hooks and TypeScript.

&nbsp;

## Table of contents
> 📌 [Technologies to be used.](#1)  
> 📌 [Creating the project.](#2)  
> 📌 [First steps.](#3)  
> 📌 [Designing the form.](#4)  
> 📌 [Implementing Formik in our form.](#5)  
> 📌 [Designing our dynamic form.](#6)  
>> 📌 [Creating the Input component.](#7)  
>> 📌 [Creating the Checkbox component.](#8)  
>> 📌 [Creating the RadioGroup component.](#9)  
>> 📌 [Creating the Select component.](#10)  
>
> 📌 [Creating the form object.](#11)  
> 📌 [Generating the validation rules using functions.](#12)  
>> 📌 [Initializing the form values.](#13)  
> 📌 [Using the getInputs function.](#14)  
> 📌 [Conclusion.](#15)  
>> 📌 [Source code.](#16)  

&nbsp;

## 🖊️ Technologies to be used. <a id="1"></a>

- ▶️ React JS (v 18)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ Formik
- ▶️ CSS vanilla (You can find the styles in the repository at the end of this post) 

## 🖊️ Creating the project. <a id="2"></a>

We will name the project: **`formik-dynamic`** (optional, you can name it whatever you like).
```bash
npm init vite@latest
```
We create the project with Vite JS and select React with TypeScript.  
Then we run the following command to navigate to the directory just created.

```bash
cd formik-dynamic
```
Then we install the dependencies.
```bash
npm install
```
Then we open the project in a code editor (in my case VS code).
```bash
code .
```

## 🖊️ First steps. <a id="3"></a>

We go to the `src/App.tsx` file and delete all the content to create a new component. That for the moment only render a `hello world`.

```tsx
const App = () => {
  return (
    <>
        <div>Hello world</div>
    </>
  )
}
export default App
```

Lo que sigue es crear un Layout, esto solo es con propósito de estética para la app, no es obligatorio.

> 🚨 Note: Each time we create a new folder, we will also create an **index.ts** file to group and export all the functions and components of other files that are inside the same folder, so that these functions can be imported through a single reference, this is known as **barrel file**. 

Create the `src/components` folder and inside create the **Layout.tsx** file to add:
```tsx
interface ILayout {
    children: JSX.Element | JSX.Element[]
    title: string
}

export const Layout = ({ children, title }: ILayout) => {
    return (
        <div className="container">
            <h2 className="title">{title}</h2>
            {children}
        </div>
    )
}
```

Then we are going to create the `src/pages` folder. The purpose is to simulate different pages since we are going to explain a basic way in which formik is usually used, then another page will be to explain the dynamic forms.

We will create a **FormikBasic.tsx** file to add the following for the moment:

```tsx
import { Layout } from "../components"

export const FormikBasic = () => {
    return (
        <Layout title="Formik Basic">
            <div>Hello world</div>
        </Layout>
    )
}
```

Then we import it in the file `src/App.tsx`.

```tsx
import { FormikBasic } from "./pages"

const App = () => {
  return (
    <>
      <FormikBasic />
    </>
  )
}
export default App
```

## 🖊️ Designing the form. <a id="4"></a>

Inside `src/components/FormikBasic.tsx` we are going to create a basic form, which uses some of the most common controls in a form.

```tsx
import { Layout } from "../components"

export const FormikBasic = () => {

    return (
        <Layout title="Formik Basic">
            <form>

                <input type="text" placeholder="Full name"/>

                <input type="email" placeholder="E-mail"/>

                <input type="password" placeholder="Password"/>

                <div>
                    <label htmlFor="rol">Select an option:</label>
                    <select id="rol" >
                        <option value="">--- Select ---</option>
                        <option value="admin">Admin</option>
                        <option value="user">User</option>
                        <option value="super">Super Admin</option>
                    </select>
                </div>

                <div className='radio-group'>
                    <b>Gender: </b>
                    <label ><input type="radio"/> Man</label>
                    <label ><input type="radio"/> Woman</label>
                    <label ><input type="radio"/> Other</label>
                </div>
                
                <label>
                    <input type="checkbox" {...getFieldProps('terms')} />
                    Terms and Conditions
                </label>

                <button type="submit">Submit</button>
            </form>
        </Layout>
    )
}
```

Already with styles, it should look like this 👀:

![basic form](https://res.cloudinary.com/dnxchppfm/image/upload/v1664039607/formik/01_xzlfn1.png)




## 🖊️ Implementing Formik in our form. <a id="5"></a>

We proceed to install [Formik](https://formik.org/docs/overview) to manage our form and another very useful package will be [Yup](https://formik.org/docs/guides/validation) to manage the validations of our form.

```tsx
npm install formik yup
```

Normally to use Formik and manage our forms we would do it through the hook **useFormik**.

To the hook use Formik we will pass an object with 3 properties (it has more properties but we will only use these 3) <a id="3props"></a>

- **initialValues**, is an object with the initial values of our form, that makes reference to each input or field. 
- **validationSchema**, this is basically where we will use Yup to establish the validations to our respective form fields.
- **onSubmit**, is a function that receives the values of the form and is only executed when all the validations are passed.

```tsx
useFormik({
    initialValues:,
    validationSchema: ,
    onSubmit:
});
```

**initialValues**, will have an object defining each field of the form and will be initialized with an empty string and the **terms** field will be a boolean value initialized to false.

**validationSchema**, will have a Yup.object (Don't forget to import Yup at the top of the file: `import * as Yup from 'yup'` ), which is a function that receives an object defining the validations. (Note that the keys of each property must match the properties of **initialValues**). Inside each property will have its validation rules

**onSubmit**, in fact it will do nothing more than show the values in console.

```ts
const { handleSubmit, errors, touched, getFieldProps } = useFormik({
    initialValues: {
        fullName: '',
        email: '',
        password: '',
        rol: '',
        gender: '',
        terms: false
    },
    validationSchema: Yup.object({
        fullName: Yup.string().min(3, 'Min. 3 characters').required('Required'),
        email: Yup.string().email('It should be a valid email').required('Required'),
        password: Yup.string().min(6, 'Min. 6 characters').required('Required'),
        terms: Yup.boolean().isTrue('You must accept the terms!'),
        rol: Yup.string().required('Required'),
        gender: Yup.string().required('Required'),
    }),
    onSubmit: values => {
        console.log(values)
    }
});
```

The hook returns several properties and functions, but we only need:

- **handleSubmit**, it is the function that you must pass to your form to make the form post. This function must be executed in the onSubmit event of the form tag.

- **errors**, is an object with the errors of each field, identified with the name of the properties that you placed in **initialValues**.

- **touted**, indicates if the input has been touched, this will serve to execute the validations of that field and to show the error after the input has been touched and not at the beginning of the application when the user just sees the form. This prop, is an object whose props are identified with the name of the properties that you placed in **initialValues**.
 
- **getFieldProps**, is a getter function that brings us different attributes necessary for the input to work (name, value, onChange, onBlur) that generally we can also obtain them of the useFormik, but it would be more code to have to place each property (it is useful when we have to do something specific with this property. but in this case not). It receives as parameter a name, that must with some property of **initialValues**.

```ts
const { handleSubmit, errors, touched, getFieldProps } = useFormik({
    // ...props
});
```

Now that we have our hook ready, we will modify our JSX.

First in the form tag we place the handleSubmit and the noValidate.
```html
<form noValidate onSubmit={handleSubmit}>
```

Now in the input of type text, email and password, we place the following.

We spread the properties that getFieldProps returns (it receives as parameter a name, which must have some property of **initialValues**).

The className we do the validation where if the input was touched and there is the corresponding error with that input, that the class 'error_input' is added. (although I never actually use that class in the styles).


```tsx
<input
    // ...attr
    {...getFieldProps('password')}
    className={`${(touched.password && errors.password) && 'error_input'}`}
/>
```

Such a condition in the className attribute can be used to display the error message:

```tsx
{(touched.password && errors.password) && <span className="error">{errors.password}</span>}
```

In the case of the select and the input of the checkbox type, we will only add the getFieldProps and spread the values returned by this function.

```tsx
<select id="rol" {...getFieldProps('rol')} >
    // ...options
</select>

<input type="checkbox" {...getFieldProps('terms')} />
```

In the case of the radio type input.
We add the getFieldProps and we spread the values returned by this function.

We will add a value. 

In the checked property we will evaluate if the value property of getFieldProps is equal to the value of our input, then it must be active that input radio. 

```tsx
<input type="radio"
    {...getFieldProps('gender')}
    value='women'
    checked={getFieldProps('gender').value === 'women'}
/>
```

Our entire component would look as follows 👀:

```tsx
import * as Yup from 'yup';
import { useFormik } from "formik";
import { Layout } from "../components"

export const FormikBasic = () => {

    const { handleSubmit, errors, touched, getFieldProps } = useFormik({
        initialValues: {
            fullName: '',
            email: '',
            password: '',
            rol: '',
            gender: '',
            terms: false
        },
        validationSchema: Yup.object({
            fullName: Yup.string().min(3, 'Min. 3 characters').required('Required'),
            email: Yup.string().email('It should be a valid email').required('Required'),
            password: Yup.string().min(6, 'Min. 6 characters').required('Required'),
            terms: Yup.boolean().isTrue('You must accept the terms!'),
            rol: Yup.string().required('Required'),
            gender: Yup.string().required('Required'),
        }),
        onSubmit: values => {
            // TODO: some action
        }
    });

    return (
        <Layout title="Formik Basic">
            <form noValidate onSubmit={handleSubmit}>

                <input
                    type="text"
                    placeholder="Full name"
                    {...getFieldProps('fullName')}
                    className={`${(touched.fullName && errors.fullName) && 'error_input'}`}
                />
                {(touched.fullName && errors.fullName) && <span className="error">{errors.fullName}</span>}
                <input
                    type="email"
                    placeholder="E-mail"
                    {...getFieldProps('email')}
                    className={`${(touched.email && errors.email) && 'error_input'}`}
                />
                {(touched.email && errors.email) && <span className="error">{errors.email}</span>}
                <input
                    type="password"
                    placeholder="Password"
                    {...getFieldProps('password')}
                    className={`${(touched.password && errors.password) && 'error_input'}`}
                />
                {(touched.password && errors.password) && <span className="error">{errors.password}</span>}
                <div>
                    <label htmlFor="rol">Select an option:</label>

                    <select id="rol" {...getFieldProps('rol')} >
                        <option value="">--- Select ---</option>
                        <option value="admin">Admin</option>
                        <option value="user">User</option>
                        <option value="super">Super Admin</option>
                    </select>
                </div>


                <div className='radio-group'>
                    <b>Gender: </b>
                    <label >
                        <input type="radio"
                            {...getFieldProps('gender')}
                            checked={getFieldProps('gender').value === 'man'}
                            value='man'
                        />
                        Man
                    </label>
                    <label >
                        <input type="radio"
                            {...getFieldProps('gender')}
                            checked={getFieldProps('gender').value === 'women'}
                            value='women'
                        />
                        Woman
                    </label>
                    <label >
                        <input type="radio"
                            {...getFieldProps('gender')}
                            checked={getFieldProps('gender').value === 'other'}
                            value='other'
                        />
                        Other
                    </label>

                    {(touched.gender && errors.gender) && <span className="error">{errors.gender}</span>}
                </div>
                {(touched.rol && errors.rol) && <span className="error">{errors.rol}</span>}
                <label>
                    <input type="checkbox" {...getFieldProps('terms')} />
                    Terms and Conditions
                    {(touched.terms && errors.terms) && <span className="error">{errors.terms}</span>}
                </label>

                <button type="submit">Submit</button>
            </form>
        </Layout>
    )
}
```
So far we have a basic and functional use of a form with its validations.


## 🖊️ Designing our dynamic form. <a id="6"></a>
Now we are going to create a new page, in our `src/pages` we create **FormikDynamic.tsx**.
And for the moment we add:

```tsx
import { Layout } from "../components"

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">
            <div>Hello world</div>
        </Layout>
    )
}
```

And this component is shown in `src/App.tsx`.

```tsx
import { FormikBasic, FormikDynamic } from "./pages"

const App = () => {
  return (
    <>
      <FormikDynamic />
      {/* <FormikBasic /> */}
    </>
  )
}
export default App
```


Inside `src/components/FormikDynamic.tsx` we are going to add the components that Formik offers us to manage a form.

We import the Formik component, whose props are similar to the **useFormik** hook and that at the same time we will use the same [**3 properties mentioned above**](#3props). Then we will set the initialValues and validationSchema.

```tsx
import { Formik } from "formik"

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">

            <Formik
                initialValues={{}}
                validationSchema={{}}
                onSubmit={ values => console.log(values) }
            > 
            
            </Formik>
        </Layout>
    )
}
```

The Formik component uses the "render props" pattern and for this purpose it receives a function inside the component. This function also returned certain properties as does the hook useFormik, but in this case we will not use them.

The function, is going to render another component of formik that is the Form since it is similar to the form tag but that already has the handleSubmit.

```tsx
import { Formik } from "formik"

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">

            <Formik
                initialValues={{}}
                validationSchema={{}}
                onSubmit={ values => console.log(values) }
            > 
            {
                () => (
                    <Form noValidate>
                    
                        <button className="btn btn_submit" type="submit">Submit</button>
                    </Form>
                )
            }         
            </Formik>
        </Layout>
    )
}
```

Now we need the inputs, but in this case we will separate them into reusable components.

### ✏️ Creating the Input component. <a id="7"></a>

Inside the folder `src/components` we create the file **CustomTextInput.tsx**.

In the file we create a component and define the interface of the props that will arrive to the component. 

The name is one of the most important parts to identify the input. 

At the end we put `[x: string]: any` because if you need to put some other prop, you do not have to establish it in the interface avoiding that it grows (it is optional, if you want to define each property you can do it).

For example, in the interface we don't have defined the prop **autoComplete** but when we use the component, it won't error if we put as props autoComplete and its value ('off' / 'on').
 
We add a simple input without attributes.

```tsx
interface Props {
    name: string;
    type: string;
    placeholder?: string;
    [x: string]: any
}

export const CustomTextInput = (props: Props) => {
    return ( <input /> )
}
```

Now, we will use the **useField** hook provided by formik. This hook is the key to connect the inputs to Formik.

The **useField** hook takes either an object or a string as an argument, but **you must always send it the name of the input**. In this case there is no problem sending the whole **prop** object, or just **prop.name**.

The hook returns an array with three positions.

- **FieldProps**, contains everything necessary for the input to work onChange, value, onBlur, etc.

- **FieldMetaProps**, contains values computed on the field that can be used to style or change the field, such as touched and errors.

- **FieldHelperProps**, contains helper functions that allow to imperatively change the values of a field. For example setValue.

The one that interests us is the value of the first position of the FieldProps, and we spread both the props that arrive to the component and the value of **field** that gives us the hook.

```tsx
import { useField } from "formik"

interface Props {
    name: string;
    type: string;
    placeholder?: string;
    [x: string]: any
}

export const CustomTextInput = (props: Props) => {

    const [field] = useField(props)

    return (
        <input {...field} {...props} />
    )
}
```
You could also use the second position (the FieldMetaProps) to display errors, which would be exactly the same as in Formik basic. But we better use a component provided by formik to display the errors, the **ErrorMessage**.

The **ErrorMessage** receives mandatory the **name** of the input, by default it does not only show the text, without HTML tag so we place the component property to tell it to render a span.

```tsx
import { ErrorMessage, useField } from "formik"

interface Props {
    name: string;
    type: string;
    placeholder?: string;
    [x: string]: any
}

export const CustomTextInput = (props: Props) => {

    const [field] = useField(props)

    return (
        <>
            <input {...field} {...props} />
            <ErrorMessage name={props.name} component="span" className="error" />
        </>
    )
}
```

There we have our input.

### ✏️ Creating the Checkbox component. <a id="8"></a>
Making this input component of type checkbox is basically the same as the normal input. The only thing that changes is the JSX structure and the interface.

```tsx
import { ErrorMessage, useField } from "formik"

interface Props {
    label: string;
    name: string;
    [x: string]: any
}


export const CustomCheckBox = (props: Props) => {
    const [field] = useField(props)

    return (
        <label className="label_check">
            <input type="checkbox" {...field} {...props} />
            <span>{props.label}</span>
            <ErrorMessage name={props.name} component="span" className="error" />
        </label>
    )
}
```

### ✏️ Creating the RadioGroup component. <a id="9"></a>

This component consists of a group of radio type inputs.

It is almost the same as the previous components, only here we have an array of options that are the values and descriptions of the input.

We have to go through these options and set its value attribute to the input and also its checked attribute that will have a condition where if the value of the field is equal to the value of the input the input will be activated.

> Placing the value is necessary because this input will not change its value will always be the same, what changes is the value of the checked attribute. 

> We also put the value to identify the input, since the input radio, so that you can select only one of a group, they have to have the same attribute name.

So when the input does an onChange, formik grabs the value attribute and sets them. then it evaluates if the set value is equal to one of the values of the input group then its checked attribute will set it to bring.

```tsx
import { useField, ErrorMessage } from 'formik';

type Opt = { value: string | number, desc: string }

interface Props {
    options: Opt[]
    name: string
    label: string
    [x: string]: any
}

export const CustomRadioGroup = ({ label, options, ...props }: Props) => {
    const [field] = useField(props)

    return (
        <div className='radio-group'>
            <b>{label}</b>
            {
                options.map(opt => (
                    <label key={opt.value}>
                        <input
                            {...field}
                            {...props}
                            type="radio"
                            value={opt.value}
                            checked={opt.value === field.value}
                        />
                        {opt.desc}
                    </label>
                ))
            }
            <ErrorMessage name={props.name} component="span" className="error" />
        </div>
    )
}
```


### ✏️ Creating the Select component. <a id="10"></a>
To make this component select is almost the same as the radio group. The only thing that changes is the structure of the JSX, the select tag is the one to which the properties of the field and the ones that arrive to our component are spread.

Inside the select, we go through the options and we establish its value and description.

```tsx
import { ErrorMessage, useField } from "formik"

interface Props {
    options: Opt[]
    label: string;
    name: string;
    [x: string]: any
}

type Opt = { value: string | number, desc: string }

export const CustomSelect = ({ label,options, ...props }: Props) => {
    const [field] = useField(props)

    return (
        <>
            <div>
                <label htmlFor={props.name || props.id}> {label} </label>

                <select {...field} {...props} >
                    
                    <option value="">--- Select ---</option>

                    {
                        options.map(({ desc, value }) => (
                            <option
                                value={value}
                                key={value}
                            >{desc}</option>
                        ))
                    }

                </select>
            </div>
            <ErrorMessage name={props.name} component="span" className="error" />
        </>
    )
}
```



## 🖊️ Creating the form object. <a id="11"></a>
Here we will define how we want our form, it could be either a JSON file, but in this case I will do it with an object to place the types from the beginning.

We create some interfaces.

First we have the InputProps where we have the basic properties of an input and at the end we have 4 properties that are:
-**type**, will be used to know which component to render.
-**typeValue**, will be used to know what type of data to assign to the instance of Yup.
-**options**, are the input radio or the options of a select. 
-**validations**, the validation rules.

Then we have the Opt interface that we had already used before in the CustomRadioGroup and CustomSelect, you can even create an interface file to reuse them.

Finally we have the Validation interface to set the validation rules with Yup.
-**type**, is the type of validation we want to implement to the field.
-**value**, the value (optional) that we will set to the validation.
-**message**, the custom message to display.

```tsx
export interface InputProps {
    name: string
    value: string | number | boolean
    placeholder?: string
    label?: string

    type: 'text' | 'radio-group' | 'email' | 'password' | 'select' | 'checkbox'
    typeValue?: 'string' | 'boolean'
    options?: Opt[]
    validations: Validation[]
}

export interface Opt {
    value: string | number
    desc: string
}

export interface Validation {
    type: 'required' | 'isEmail' | 'minLength' | 'isTrue'
    value?: string | number | boolean
    message: string
}
```

Based on the interfaces we export a constant that contains an object with the forms, which in this case is only going to be a form.

Here we have just created exactly the basic form that we have done previously.

```tsx

export const forms: { [x: string]: InputProps[] } =
{
    login: [
        {
            type: "text",
            name: "name",
            placeholder: "Full Name",
            value: "",
            validations: [
                {
                    type: "minLength",
                    value: 3,
                    message: "Min. 3 characters",
                },
                {
                    type: "required",
                    message: "Full Name is required"
                },
            ],

        },
        {
            type: "email",
            name: "email",
            placeholder: "E-mail",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Email is required"
                },
                {
                    type: "email",
                    message: "Email no valid"
                }
            ],

        },
        {
            type: "password",
            name: "password",
            placeholder: "Password",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Password is required"
                }
            ],

        },
        {
            type: "select",
            name: "rol",
            label: "Select an option: ",
            value: "",
            options: [
                {
                    value: "admin",
                    desc: "Admin",
                },
                {
                    value: "user",
                    desc: "User"
                },
                {
                    value: "super-admin",
                    desc: "Super Admin"
                }
            ],
            validations: [
                {
                    type: "required",
                    message: "Rol is required"
                }
            ]
        },
        {
            type: "radio-group",
            name: "gender",
            label: "Gender: ",
            value: "",
            options: [
                {
                    value: 'man',
                    desc: "Man"
                },
                {

                    value: "woman",
                    desc: "Woman"
                },
                {

                    value: "other",
                    desc: "Other"
                },
            ],
            validations: [
                {
                    type: "required",
                    message: "Gender is required"
                }
            ]
        },
        {
            type: "checkbox",
            name: "terms",
            typeValue: "boolean",
            label: "Terms and Conditions",
            value: false,
            validations: [
                {
                    type: "isTrue",
                    message: "Accept the terms!"
                }
            ]
        },
    ],
}
```

Now it is time to create a function to build each validation rule and the initial values of the form


## 🖊️ Generating the validation rules using functions. <a id="12"></a>

We go to `src/utils` and inside we create a file called **getInputs.ts**.
Where we are going to have the first function:

`generateValidations`, it receives only one parameter: 
- **field**, the first one is the field with all its props, although we only need the validations and the type of value that is the field.

```tsx
const generateValidations = (field: InputProps) => {}
```

Then we need to create an empty schema, which we are going to reassign its value.

But for the moment the schema can be a string or a boolean, since the only boolean value is the checkbox and the others are string, but it will accept other primitive values.

```tsx
const generateValidations = (field: InputProps) => {
    let schema = Yup[field.typeValue ? field.typeValue : 'string']() // Yup.string() 
}
```

We will then step through each field validation. The validations are found in **field.validations**.

```tsx
import * as Yup from "yup";
import { InputProps } from './forms';

const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue ? field.typeValue : 'string']()

    for (const rule of field.validations) {}
}
```

Then, we are going to evaluate the type of validation that the field has, and we will do it with a switch.

By default, only the required validation and the message are added to the schema.

```tsx
import * as Yup from "yup";
import { InputProps } from './forms';

const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue ? field.typeValue : 'string']()

    for (const rule of field.validations) {
        switch (rule.type) {

            default: schema = schema.required(rule.message); break;
        }
    }

}
```

Finally we add the other validations and return the **schema**.

```tsx
import * as Yup from "yup";
import { AnyObject } from "yup/lib/types";
import { InputProps } from './forms';

type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>
type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>

const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue ? field.typeValue : 'string']()

    for (const rule of field.validations) {
        switch (rule.type) {
            
            case 'isTrue': schema = (schema as YupBoolean).isTrue(rule.message); break;

            case 'isEmail': schema = (schema as YupString).email(rule.message); break;

            case 'minLength': schema = (schema as YupString).min(rule?.value as number, rule.message); break;

            default: schema = schema.required(rule.message); break;
        }
    }

    return schema
}
```

### ✏️ Initializing the form values. <a id="13"></a>

Now we need to initialize the values of our form.

For it we are going to create a function that receives a parameter.

You will notice that in the file `src/utils/forms.ts` the constant **forms** exports an object, well the idea is that the function that we will create now receives a property that coincides with the keys of the object, for example the key **'login'**. So that this way we keep the forms in a single file.

```tsx
export const forms: { [x: string]: InputProps[] } = {
    login: [
        // ...
    ],
    // register:[
        //... 
    // ],
    // etc...
}
```

Another option is also to pass the complete form to the function.

But we will do it passing only the key.

So we create the function, inside we initialize two variables:

- **initialValues**, the initial values of our form.
- validationsFields**, the validation rules of each field of our form.

These variables will be reassigned again so we use let and initialize them as an empty object.

```tsx
type Form = 'login'

export const getInputs = (section: Form) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};
};
```


Then we go through the form, accessing its section


```tsx
import { forms } from './forms';

type Form = 'login'

export const getInputs = (section: Form) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

    for (const field of forms[section]) {}
};
```

Inside the loop: 
1 - We use the initialValues variable to compute the field name and assign the default value of the field.

2 - We will make a condition where if there is no validation for the field, we just place **continue** so that it just exits the loop but executes the rest of the code that is after the for of loop.

3 - Then we use the function to generate the [validation scheme we created earlier](#12). and assign it to a constant.

4 - At the end of the loop, we use the validationsFields variable to compute the field name and assign the generated schema that is in the schema variable.

5 - Finally, after the cycle we return an object with the validation rules, the initial values of the form and the inputs.

```tsx
import { forms } from './forms';

type Form = 'login'

export const getInputs = (section: Form) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

    for (const field of forms[section]) {

        initialValues[field.name] = field.value;

        if (!field.validations) continue;

        const schema = generateValidations(field)

        validationsFields[field.name] = schema;
    }

    return {
        validationSchema: Yup.object({ ...validationsFields }),
        initialValues,
        inputs: forms[section],
    };
};
```

The whole file would look like this (you can separate it into different ones if you want to) 👀

```tsx
import * as Yup from "yup";
import { AnyObject } from "yup/lib/types";
import { forms, InputProps } from './forms';

type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>
type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>

const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue ? field.typeValue : 'string']()

    for (const rule of field.validations) {
        switch (rule.type) {
            case 'isTrue': schema = (schema as YupBoolean).isTrue(rule.message); break;
            case 'isEmail': schema = (schema as YupString).email(rule.message); break;
            case 'minLength': schema = (schema as YupString).min(rule?.value as number, rule.message); break;
            default: schema = schema.required(rule.message); break;
        }
    }

    return schema
}

type Form = 'login'

export const getInputs = (section: Form) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

    for (const field of forms[section]) {

        initialValues[field.name] = field.value;

        if (!field.validations) continue;

        const schema = generateValidations(field)

        validationsFields[field.name] = schema;
    }

    return {
        validationSchema: Yup.object({ ...validationsFields }),
        initialValues,
        inputs: forms[section],
    };

};
```

## 🖊️ Using the getInputs function. <a id="14"></a>
We go back to the `src/pages/FormikDynamic.tsx` file and outside the component we use our **getInputs** function, sending the section we want as parameter, and obtaining the returned values

```tsx
const { initialValues, inputs, validationSchema } = getInputs('login')
```

The initial values and the validation scheme are assigned to the **Formik** component.

```tsx
<Formik
    initialValues={initialValues}
    validationSchema={validationSchema}
    onSubmit={(values) => { console.log(values) }}
>
// ...
```

Now inside the **Form** component, we are going to iterate with the map function, the inputs that we get from the getInputs.

We are going to evaluate the type of input that we are going to render using a switch. And depending on each type, we render a component and we will pass the necessary props, in this TypeScript will help us.

```tsx
<Form noValidate>
{
    inputs.map(({ name, type, value, ...props }) => {
        switch (type) {
            case "select":
                return <CustomSelect
                    key={name}
                    label={props.label!}
                    name={name}
                    options={props.options!}
                />

            case "radio-group":
                return <CustomRadioGroup
                    label={props.label!}
                    name={name}
                    options={props.options!}
                    key={name} />

            case "checkbox":
                return <CustomCheckBox
                    label={props.label!}
                    key={name}
                    name={name}
                />

            default:
                return <CustomTextInput
                    key={name}
                    name={name}
                    placeholder={props.placeholder}
                    type={type}
                />
    }
})
}
```

The component would look like this 👀:

```tsx
import { Form, Formik } from "formik"
import { CustomCheckBox, CustomRadioGroup, CustomTextInput, CustomSelect, Layout } from "../components"
import { getInputs } from "../utils"

const { initialValues, inputs, validationSchema } = getInputs('login')

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">

            <Formik
                {...{ initialValues, validationSchema }}
                onSubmit={(values) => { console.log(values) }}
            >
                {
                    () => (
                        <Form noValidate>
                            {
                                inputs.map(({ name, type, value, ...props }) => {
                                    switch (type) {
                                        case "select":
                                            return <CustomSelect
                                                key={name}
                                                label={props.label!}
                                                name={name}
                                                options={props.options!}
                                            />

                                        case "radio-group":
                                            return <CustomRadioGroup
                                                label={props.label!}
                                                name={name}
                                                options={props.options!}
                                                key={name} />

                                        case "checkbox":
                                            return <CustomCheckBox
                                                label={props.label!}
                                                key={name}
                                                name={name}
                                            />

                                        default:
                                            return <CustomTextInput
                                                key={name}
                                                name={name}
                                                placeholder={props.placeholder}
                                                type={type}
                                            />
                                    }
                                })
                            }

                            <button className="btn btn_submit" type="submit">Submit</button>
                        </Form>
                    )
                }
            </Formik>
        </Layout>
    )
}
```

And that's it, so we have a dynamic form, we just modify the form file to add another form or another field to a form, add another rule, without having to modify the component.

Also, you can split the FormikDynamic component in smaller components if you want.

## 🖊️ Conclusion. <a id="15"></a>

Implementing dynamic forms is very helpful if your application has several forms, and thanks to the Formik library and the validations with Yup, it is much easier to work such a form management situation.

Another idea I can give you is, imagine you have an API that gives you a JSON with the fields and validations, it can be much more useful, instead of having a file with all the form information.

I hope you liked this post and that it helped you to understand more about how to make dynamic forms with React and Formik. 🤗

If you know any other different or better way to perform this functionality feel free to comment 🙌.

> **I invite you to check my portfolio in case you are interested in contacting me for a project!. [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 Don't forget to follow me also on twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

{%embed https://twitter.com/Frankomtz361/status/1575330172819193857?s=20&t=AVAVQ1am_AkFb2TyMo9S-A %}

### ✏️ Source code. <a id="16"></a>

{%embed https://github.com/Franklin361/dynamic-form %}