question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Set focus in first faulty field

See original GitHub issue

Hi,

is there a way to always set the focus to the first faulty field?

I tried to implement a solution in my render field component:

componentDidUpdate() {
    const { meta: { submitting, touched, error } } = this.props;
    const field = this.refs['field' + this.props.name];

    ...

    if (!submitting && touched && error) {
      field.select();
    }
}

This works partially, but…

If I enter one char in the first faulty field, the validation is at this moment valid for the field and the next faulty field steals my focus.

How can I prevent this or is there already a built-in solution?

Thanks for your help!

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:16 (5 by maintainers)

github_iconTop GitHub Comments

13reactions
AubreyHewescommented, Aug 25, 2017

All suggestions (in all corresponding/found issues) for implementing focusing the first field error were failing for me… then I worked out why… when using redux-form/immutable the onSubmitFail errors object does not contain the fields in registration order (possibly requires an ordered list reviver when setting/getting fields?).

Also most do numerous DOM queries (query per field). So I solved it with one single DOM query:

In onSubmitFail Get all the error’ed fields and query them using querySelector. This is the most workable/optimal as querySelector “Returns the first Element within the document that matches the specified selector, or group of selectors.” … so even if the selector is not in field order… it still returns the first error’ed field in the document, then just focus() it.

Could possibly also scrollIntoView() though in my case this failed as the site has a fixed/floating header.

Example

onSubmitFail: (errors) => {
  // # Focus on first error

  // When Immutable:
  // The errors are _not_ in `REGISTER_FIELD` order so we cant just "use" the first one..
  // (possibly requires an ordered list reviver when getting errors?)

  // so we try to get the first error element in another way, using a DOM query:

  // Note: You can't query a possible error class in `onSubmitFail` as the `render` may not have yet happened.

  // We do this using a DOM selector of all the fields that have error'ed and use [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) which
  // Returns the first Element within the document that matches the specified selector, or group of selectors.
  const errorEl = document.querySelector(
    // flattenObject: https://github.com/hughsk/flat/issues/52
    Object.keys(flattenObject(errors)).map(fieldName => `[name="${fieldName}"]`).join(',')
  );
  if (errorEl && errorEl.focus) { // npe
    // if (errorEl.scrollIntoView) {
    //   errorEl.scrollIntoView(); // fails as the site has a fixed/floating header
    // }
    errorEl.focus(); // this scrolls without visible scroll
  }
}

Notes

  • The form this is used for does not contain arrays, so I haven’t checked the flattenObject actually creates correct field names
  • Have not tested the selector length for large forms
  • It is not scoped; Multiple fields with the same name in the same document (i.e. multiple forms with the same field.name) will focus the first found. Though this could be resolved by accessing the passed props and adding a possible className to the selector.

#1336 #488

4reactions
dhurlburtusacommented, Jan 22, 2018

I just recently started using redux-forms. I am working on an existing project that is using version 6.8.0. (Based on the implementation and the v7 docs, I think it will work in v7 too.)

This solution uses 2 utility functions and the onSubmitFail callback.

// ReduxFormUtils.js
/**
 * Flattens the hierarchical error generated by `redux-form`.
 *
 * @param {object} error - Error object as passed to `onSubmitFail`.
 */
export function flattenError(error) {
  let outErr, outErrs = []

  _flattenError(outErrs, error, '')

  outErr = {}
  outErrs.forEach((err) => {
    outErr[err[0]] = err[1]
  });

  return outErr
}

const _flattenError = (outErrs, error, prefix) => {
  for (let name in error) {
    let value = error[name]
    if (typeof value === 'string') {
      outErrs.push([ prefix + name, value ])
    }
    else if (value instanceof Array) {
      for (let i = 0, iLen = value.length; i < iLen; ++i) {
        _flattenError(outErrs, value[i], prefix + name + '[' + i + '].')
      }
    }
    else {
      _flattenError(outErrs, value, prefix + name + '.')
    }
  }
}

export default {
  flattenError,
}
// FormUtils.js
/**
 * @param {string} formName - The name of the form which may have invalid input
 *   controls.
 * @param {object} error - An object where each key is the name of an invalid
 *   input control in the form with the specified name.
 */
export const focusFirstInvalidControl = (formName, error) => {
  let elmts, form

  form = document.forms[formName]
  elmts = (form || {}).elements || []
  for (let i = 0, iLen = elmts.length; i < iLen; ++i) {
    let elmt = elmts[i]
    if (elmt.name in error) {
      elmt.focus()
      break
    }
  }
}

export default {
  focusFirstInvalidControl,
}
import { reduxForm } from 'redux-form'

import { focusFirstInvalidControl } from './FormUtils'
import { flattenError } from './ReduxFormUtils'
...
const FORM_NAME = 'MyForm'

class MyFormComponent extends React.Component {
  ...
  render() {
    return (
      ...
      <form name={FORM_NAME} ...>
      ...
    )
  }
  ...
}
...

MyFormComponent = reduxForm({
  form: FORM_NAME,
  onSubmitFail: (error, dispatch, submitError, props) => {
    focusFirstInvalidControl(FORM_NAME, flattenError(error))
  },
  ...
})(MyFormComponent)

There are two main steps that need to be done for each form:

  1. Add a name attribute to the form element (just reuse the unique one you give to reduxForm)
  2. Call the utility functions in the onSubmitFail callback as illustrated above.

This solution solves all the issues I believe have been plaguing everyone.

  • Scoped to just one form (just in case you have multiple forms in the DOM with an invalid input control with the same name.)
  • Focuses the first invalid input element found in the form.
  • No need to try to scroll the element into view. The focusing should take care of that.
  • Handles the possibly hierarchical error object created by redux-form.

Notes:

  • I have not tested this with checkboxes or radio button controls (but I believe it should work).
  • I have not tested it in v7 of redux-forms (but I am pretty sure it should work).
  • The two utility functions could be combined into one, but I kept them separate because focusFirstInvalidControl can be used in any HTML document regardless of it being generated by React.

Please let me know if these utilities already exist in an npm package somewhere. If not and you have a cleaner implementation (maybe one that doesn’t use recursion), then please let me know.

Edit: I whipped up an npm package that will set focus on the first invalid control. See qc-dom_utils. You can use the following in place of the FormUtils.js module above.

// Import just the function needed
import focusFirstInvalidControl from 'qc-dom_utils/form/focusFirstInvalidInputControl'
// Or import just the form utilities and use `FormUtils.focusFirstInvalid`
import FormUtils from 'qc-dom_utils/form'
// Or import the whole library and use `DomUtils.form.focusFirstInvalid`
import DomUtils from 'qc-dom_utils'
...

Edit: I searched in npm and I searched the redux-form code base, but I didn’t find anything to flatten the errors. So, I created the qc-redux-form_utils package.

// Import just the function needed
import flattenErrors from 'qc-redux-form_utils/flattenErrors'
Read more comments on GitHub >

github_iconTop Results From Across the Web

Bring Focus to the First Form Field with an Error
For simplicity, and based on personal preference, I am going to start with setting up my form to validate when the form is...
Read more >
How to focus on first input of error in js where return false is ...
Option 1 - Add class to the fields with error and then you can use below code to focus the first error element....
Read more >
How to set the focus to the first form field ? - GeeksforGeeks
Approach 1: Using HTML <input> autofocus attribute. Autofocus is a boolean attribute, used along with an <input> element in a form. The form ......
Read more >
how to set the focus to the first input-field of a form?
In my case i have an ajaxed submit that refreshes only a certain zone but i have to set focus after the submit....
Read more >
How to set focus to first error field. - SAP Community
My users want the cursor positioned to the first error field on the page after validation errors occur. WDA does a nice job...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found