Formik and Yup validation not working when submitting form

by admin
react-select-async-paginate Formik

I’ve had this problem where Formik and Yup just wouldn’t show the validation for fields that were added dynamically to a form. For example if you have a a form with a list of objects. Like this:

The problem

The validation does work when you click in and out of the field. When you press the submit button however, all messages dissapear.

The reason for this is that I only show validation messages after a field is ‘touched’. Otherwise the user would be hit with lots of field validation errors as soon as they open a page or add something.

When submitting the form however, all fields should receive the touched state. This works great when all fields are defined in the initialvalues object.

When you use dynamic lists however, you can’t do that. You don’t know all items in the list beforehand. Therefore the autotouch on submit mechaniscm doesn’t work as intended.

The issue is also discussed on Github.

The solution

After some digging I found out that the errors object actually has the field errors. But they aren’t shown because the touched state is lost upon submit.

So, why not just complement the touched object based on the errors object when submitting?

In order to prevent myself from having to repeat the steps for every form. I have created a ‘generic’ Formik form submit button that handles all logic. It looks like this:

import { useFormikContext } from 'formik'
import AppButton from './AppButton'
import * as _ from 'lodash'
import { Button } from '@mui/material'

interface SubmitButtonProps {
  [x: string]: any
}

const SubmitButton = ({...otherProps }: SubmitButtonProps) => {
  const getTouchedObj = (errors: any) => {
    if (!errors) return {}

    const touched: any = {}
    Object.keys(errors).forEach(key => {
      if (Array.isArray(errors[key])) {
        touched[key] = errors[key].map((val: any) => getTouchedObj(val))
      } else if (typeof errors[key] === 'object') {
        touched[key] = getTouchedObj(errors[key])
      } else {
        touched[key] = true
      }
    })

    return touched
  }

  const { handleSubmit, setTouched, errors } = useFormikContext()

  return (
    <Button
      onClick={() => {
        setTouched(getTouchedObj(errors))

        if (_.isEmpty(errors)) {
          handleSubmit()
        }
      }}
      {...otherProps}
    >
      Save form
    </Button>
  )
}

export default SubmitButton

So what this does, is loop through all keys in the ‘Errors’ object and create a new ‘Touched’ object. That is then set as the Touched object with the ‘setTouched’ Formik handler.

The component also prevents the ‘handleSubmit’ from being called if there are any errors. Otherwise it will just keep resetting the touched state.

Small note; you need Lodash for this component. Which is awesome anyways.

You can use the button in every Formik form like this:

 <SubmitButton />

The result when pressing the ‘Save form’ button:

Thats it, Formik and Yup work nicely now.

Looking for other useful form components?

Related Posts

Leave a Comment