sajad torkamani

When a form validation error occurs, you typically want to scroll the screen to the first error. This is especially important on long forms where after clicking a submit button, the user needs to be taken to the first error.

Here’s a very rough & incomplete outline of an approach you can take with Formik though it should work even if you aren’t using Formik.

1. Create <ScrollToFirstError> component

Create a ScrollToFirstError component that looks something like this:

import React, { useEffect } from 'react'
import { useFormikContext } from 'formik'

const ScrollToFirstError: React.FC = () => {
  const { submitCount, isValid } = useFormikContext()

  useEffect(() => {
    // Wrap the code in setTimeout to make sure it runs after the DOM has been
    // updated and has the error message elements.
    // Can maybe use useLayoutEffect instead.
    setTimeout(() => {
      // Only run on submit
      if (submitCount === 0) {
        return
      }

      // Find the first error message
      const errorMessageSelector = '[data-field-error]'
      const firstErrorMessage = document.querySelector(errorMessageSelector)
      if (!firstErrorMessage) {
        console.warn(
          `Form failed validation but no error field was found with selector: ${errorMessageSelector}`
        )
        return
      }

      // Find the first label with an error
      const inputId = firstErrorMessage.getAttribute('data-field-error')
      const errorLabelSelector = `label[for="${inputId}"]`
      const firstErrorLabel = document.querySelector(errorLabelSelector)

      if (!firstErrorLabel) {
        console.warn(
          `Could not find an error label with selector: ${errorLabelSelector}`
        )
        return
      }

      firstErrorLabel.scrollIntoView()
    }, 100)
  }, [submitCount, isValid])

  return null
}

export default ScrollToFirstError

2. Use component inside Formik

import { Form, Formik } from 'formik'

const MyForm: React.FC = () => {
  return (
    <Formik>
      <Form>
         <ScrollToFirstError />
         // ....
      </Form>
    </Formik>
  )
}

Or create a <FormikEffect> component that takes an onError callback:

import { Form, Formik } from 'formik'
import { scrollToFirstError } from '../lib/utils'

const MyForm: React.FC = () => {
  return (
    <Formik>
      <Form>
         <FormikEffect
           onError={scrollToFirstError}
         />
         // ....
      </Form>
    </Formik>
  )
}

Sources / related links

TODO

  • It’d be better to only scroll to the first error if the first error is not visible in the viewport. Otherwise, the screen jumps a bit and UX becomes a bit jarring.
  • It’s probably better to create a wrapper component (e.g., <EnhancedFormik>) that takes a onError callback. That way, we don’t have to create a component just for a side effect – which feels a bit iffy.