Skip to content

Instantly share code, notes, and snippets.

@dphrag
Created November 1, 2018 23:41
Show Gist options
  • Save dphrag/4db3b453e02567a0bb52592679554a5b to your computer and use it in GitHub Desktop.
Save dphrag/4db3b453e02567a0bb52592679554a5b to your computer and use it in GitHub Desktop.
Formik Scroll To First Invalid Element W/O Refs
import React from 'react';
import { connect } from 'formik';
class ErrorFocus extends React.Component {
componentDidUpdate(prevProps) {
const { isSubmitting, isValidating, errors } = prevProps.formik;
const keys = Object.keys(errors);
if (keys.length > 0 && isSubmitting && !isValidating) {
const selector = `[id="${keys[0]}"]`;
const errorElement = document.querySelector(selector);
errorElement.focus();
}
}
render() {
return null;
}
}
export default connect(ErrorFocus);
@dipeshhkc
Copy link

dipeshhkc commented Jun 10, 2021

(1/3)Snippet to getting first error(works even if nested case)

import { isObject } from "lodash";
export const getFirstErrorKey = (object: any, keys: string[] = []): any => {
  let firstErrorKey = "";
  if (Array.isArray(object)) {
    for (let i = 0; i < object.length; i++) {
      if (object[i]) {
        firstErrorKey = Object.keys(object)[i];
        break;
      }
    }
  } else {
    firstErrorKey = Object.keys(object)[0];
  }

  if (firstErrorKey && isObject(object[firstErrorKey])) {
    return getFirstErrorKey(object[firstErrorKey], [...keys, firstErrorKey]);
  }
  return [...keys, firstErrorKey].join(".");
};

(2/3)Snippet to focus to that error input

import { getFirstErrorKey } from "./getFirstErrorKey";

export const focusElement = (errors: any) => {
  let element = null;
  const firstErrorKey = getFirstErrorKey(errors);

  if (global.window.document.getElementsByName(firstErrorKey).length) {
    element = global.window.document.getElementsByName(firstErrorKey)[0];
    if (element instanceof HTMLInputElement) {
      element.focus();
    } else {
      element = element.getElementsByTagName("input")[0];
      if (element instanceof HTMLInputElement) {
        element.focus();
      }
    }
  }
};

(3/3)Final Usage

 useEffect(() => {
    if (!isValid && submitCount > 0) {
      focusElement(errors);
    }
  }, [submitCount, isValid]);

@talkohavy
Copy link

talkohavy commented Aug 18, 2021

Amazing guys! Thanks to all of you! :)
Quick note for those of you who use smooth scroll and are not getting the offset correctly,
I used this:

const pos = errorElement.style.position;

const top = errorElement.style.top;

errorElement.style.position = 'relative';

errorElement.style.top = '-100px';

errorElement.scrollIntoView({ behavior: 'smooth', block: 'start' });

errorElement.style.top = top;

errorElement.style.position = pos;

@KristianLake
Copy link

This is great :) (using the react hooks version), I don't think it works with react-select elements though

@S-coder-lx
Copy link

All good but not working with react-select

@dayoolacodes
Copy link

anybody able to fix for react-select?

@rita-scaletech
Copy link

I used this for ReactSelect :

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

const ScrollToFieldError = ({ scrollBehavior = { behavior: 'smooth', block: 'center' } }) => {
		const { submitCount, isValid, errors } = useFormikContext();

		useEffect(() => {
			if (isValid) return;

			const fieldErrorNames = getFieldErrorNames(errors);
			let element;
			if (fieldErrorNames[0].includes('.type')) {
				element = document.querySelector(`input[aria-label='${fieldErrorNames[0]}']`);
			} else {
				element = document.querySelector(`input[name='${fieldErrorNames[0]}']`);
			}

			if (!element) return;

			// Scroll to first known error into view
			element.scrollIntoView(scrollBehavior as any);

			// Formik doesn't (yet) provide a callback for a client-failed submission,
			// thus why this is implemented through a hook that listens to changes on
			// the submit count.
		}, [submitCount]);

		return null;
};

Put ScrollToFieldError within formiks Form.
Pass aria-label props in ReactSelect .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment