Last active
March 4, 2019 07:24
-
-
Save osv/691efa00784def6f6004d31cd6108f56 to your computer and use it in GitHub Desktop.
React Formik, scroll to first element in DOM that is not valid and has error (Hook + Typescript version)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { connect, FormikContext, getIn } from 'formik'; | |
import * as React from 'react'; | |
import { useEffectWhenCountIncremented } from './reactHooks'; | |
import { PageContext } from './context'; | |
const FormikGotoErrorOnSubmitEffectFn: React.FunctionComponent<{ | |
formik?: FormikContext<any>; | |
}> = ({ formik }) => { | |
const { openAllSections } = React.useContext(PageContext); | |
const validateFinishedCount = | |
formik && // if component is in React formik's context | |
formik.isSubmitting && // and submitting | |
!formik.isValidating && // and validation is finished | |
Object.keys(formik.errors) && // and has errors | |
formik.submitCount; // than return submit count | |
useEffectWhenCountIncremented(validateFinishedCount, () => { | |
const { errors } = formik!; | |
// Uncollapse sections. In this example used context which cause rerendering this component, | |
// that is why used useEffectWhenCountIncremented | |
openAllSections(); | |
setTimeout(() => { | |
// Find first element which id or name is path to error and scroll to it | |
// tslint:disable:no-string-literal | |
const errorElement = Array.from(document.querySelectorAll('form [id], form [name]')).find( | |
el => | |
(el.id && (getIn(errors, el.id) || getIn(errors, el.id.replace('_', '.')))) || | |
(el['name'] && getIn(errors, el['name'])), | |
); | |
if (errorElement) { | |
errorElement.scrollIntoView({ behavior: 'smooth' }); | |
if (errorElement['focus']) { | |
setTimeout(() => { | |
(errorElement as any).focus(); | |
}, 250); // Focus only after 250ms when scroll animation if finished | |
} | |
} | |
}, 100); // Ensure everything is rendered to make sure animation will be smooth | |
}); | |
return null; | |
}; | |
export const FormikGotoErrorOnSubmitEffect = connect(FormikGotoErrorOnSubmitEffectFn); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Call cb ONLY once if count is > 0 and ONLY if count is incremented next time. | |
export function useEffectWhenCountIncremented(count: any, cb: (count: number) => void): void { | |
const [lastCount, setLastCount] = React.useState(0); | |
React.useMemo( | |
() => { | |
if (typeof count === 'number' && count > lastCount) { | |
cb(count); | |
setLastCount(count); | |
} | |
}, | |
[count], | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage
discussion thread: jaredpalmer/formik#146 (comment)