Scroll to the first validation error in React

To scroll to the first validation error in React, create a <ScrollToFirstError> component which is responsible for scrolling to the first error in a form. This component can be put inside a form. We are passing the submit count and errors object as props to the <ScrollToFirstError> component so that when the submit count changes, the component will automatically scroll to the first error of the form.

In this tutorial,  we are going to create a signup form and we are using Formik for the form management and Yup for validations. 

Create <ScrollToFirstError> component for scroll to the first validation error

Let us create a <ScrollToFirstError> component first.

import { useEffect } from "react";

export const getFieldErrorNames = (formikErrors) => {
  const transformObjectToDotNotation = (obj, prefix = "", result = []) => {
    Object.keys(obj).forEach((key) => {
      const value = obj[key];
      if (!value) return;

      const nextKey = prefix ? `${prefix}.${key}` : key;
      if (typeof value === "object") {
        transformObjectToDotNotation(value, nextKey, result);
      } else {
        result.push(nextKey);
      }
    });

    return result;
  };

  return transformObjectToDotNotation(formikErrors);
};

const ScrollToFieldError = ({ submitCount, errors }) => {
  useEffect(() => {
    const fieldErrorNames = getFieldErrorNames(errors);
    if (fieldErrorNames.length <= 0) return;

    const elements = document.getElementsByName(fieldErrorNames[0]);
    const element = elements[0];
    if (!element) return;

    // Scroll to first known error into view
    element.scrollIntoView({ behavior: "smooth", block: "center" });
  }, [submitCount]); // eslint-disable-line react-hooks/exhaustive-deps

  return null;
};

export default ScrollToFieldError;

Here are take submitCount and errors object as props. When submitCount changes, getFieldErrorNames(errors) function will return all the keys(keys will be the same as the field name in the case of Formik and yup) and take the first key in the array. Then scroll to that particular element by scrollIntoView({ behavior: "smooth", block: "center" }) function.

Use <ScrollToFirstError> in the form

We are using Formik and yup for Form Management.

You can install Formik and Yup using the below commands.

npm install formik yup

Our formik will looks like this:

const formik = useFormik({
    initialValues: {
      full_name: "",
      email: "",
      password: "",
      confirm_password: ""
    },
    validationSchema: Yup.object({
      full_name: Yup.string()
        .min(2, "Mininum 2 characters")
        .max(15, "Maximum 15 characters")
        .required("Required!"),
      email: Yup.string()
        .email("Invalid email format")
        .required("Required!"),
      password: Yup.string()
        .min(8, "Minimum 8 characters")
        .required("Required!"),
      confirm_password: Yup.string()
        .oneOf([Yup.ref("password")], "Password's not match")
        .required("Required!")
    }),
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2));
    }
  });

Next, use our <ScrollToFirstError> and pass submitCount and errors as:

<ScrollToFirstError submitCount={formik.submitCount} errors={formik.errors}/>

Our form will look like:

<div className="App">
      <h1>Validation with Formik + Yup</h1>

      <form onSubmit={formik.handleSubmit}>
        <ScrollToFirstError submitCount={formik.submitCount} errors={formik.errors}/>
        <div>
          <label>Full Name</label>
          <input
            type="text"
            name="full_name"
            value={formik.values.full_name}
            onChange={formik.handleChange}
          />
          {formik.errors.full_name && formik.touched.full_name && (
            <p>{formik.errors.full_name}</p>
          )}
        </div>
        <div>
          <label>Email</label>
          <input
            type="email"
            name="email"
            value={formik.values.email}
            onChange={formik.handleChange}
          />
          {formik.errors.email && formik.touched.email && (
            <p>{formik.errors.email}</p>
          )}
        </div>
        <div>
          <label>Password</label>
          <input
            type="password"
            name="password"
            value={formik.values.password}
            onChange={formik.handleChange}
          />
          {formik.errors.password && formik.touched.password && (
            <p>{formik.errors.password}</p>
          )}
        </div>
        <div>
          <label>Confirm Password</label>
          <input
            type="password"
            name="confirm_password"
            value={formik.values.confirm_password}
            onChange={formik.handleChange}
          />
          {formik.errors.confirm_password &&
            formik.touched.confirm_password && (
              <p>{formik.errors.confirm_password}</p>
            )}
        </div>
        <div>
          <button type="submit">Submit</button>
        </div>
      </form>
    </div>

The <ScrollToFirstError> component can also be used without formik and yup. But make sure the error object keys are the same as the field name because, inside the <ScrollToFirstError> component, the scrolling element is identified by its name.

Conclusion

We have created a <ScrollToFirstError> component which is responsible for scrolling to the first error. This component can be placed inside any large form. Make sure to pass the submit count and errors object as props to the <ScrollToFirstError> component. When the submit count changes, the component will automatically scroll to the first error of the form.