react-hook-form: errors[name] with nested fields always returns undefined

Describe the bug

if name is something like myprop[0], errors[‘myprop[0]’] returns undefined, so the validation message cannot be shown.

<input name="chk[0]" ref={register({required:"This is required"})} />

in this case errors[‘chk[0]’] is undefined, but validation fails, so the form cannot be submitted and the error message cannot be displayed.

I have tried to provide a codesandbox but there is a nasty bug in codesandbox (https://github.com/codesandbox/codesandbox-client/issues/667) and it was impossible to make it work.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 18 (7 by maintainers)

Most upvoted comments

Now you can import the utility function “get” directly from react-hook-form and use it like this :

import { useFormContext , get } from 'react-hook-form';
const { control, formState } = useFormContext();
const error = get(formState.errors, props.name);
// error.message to show the error message

I think I’m running into a similar type of issue (I’m upgrading from react-hook-form@3.28 to latest, haha). I had a reusable field component that uses FormContext and accesses a field’s errors similar to the examples in the docs

...
  const { errors, register } = useFormContext();
  const errorMessage = errors[name]?.message`
...

where name could be a nested value like "user.firstName". In v3 this worked just fine because the errors object was flat, and errors["user.firstName"]?.message was valid, however now I would need to access the property via errors.?user?.firstName?.message very explicitly, which I can’t generate dynamically 😢 (I don’t think, hopefully I’m just unaware of the fix 😅 ).

I’m able to work around this using lodash/get but I was very happy earlier being able to fully use dynamic properties + TS + optional chaining with the flat errors. Example workaround (very gross because of the type casting)

// `name` === "user.firstName"
const errorMessage = (get(errors, name) as FieldError)?.message;

I absolutely love this library and it’s made things so much nicer than Redux-form / Formik, this is just a little nit since I feel dirty having to bring back lodash haha ❤️

I have a feeling some of your input ref is not been forward or wired-up which result in input is not bee focused, but in terms of first errors lookup, your approach is correct. However, if you have flattened or data it’s probably easier with a single exit loop.

Yeah I have something like this now (very quickly hacked together - and not very pretty)

export function scrollToFirstFormError(form) {
  let path = '';
  const scrollToFirstError = (err) => {
    const errorRecord = Object.values(err).find((element, index) => {
      if (isObject(element)) {
        path = path
          ? `${path}.${Object.keys(err)[index]}`
          : path + Object.keys(err)[index];
        return true;
      }
      return false;
    });

    if (errorRecord?.ref) {
      const element = document.getElementsByName(path)[0];
      if (element) {
        element.scrollIntoView({ block: 'center' });
        element.focus();
      }
    } else if (errorRecord) {
      scrollToFirstError(errorRecord);
    }
  };
  return scrollToFirstError(form?.formState?.errors);
}

You need to get single field error in fieldState in Controller render prop instead of formState errors object for all fields:

render={({ fieldState: { error }, field: { onChange, value } }) => {

I truly do, it’s such a small nitpick in the face of so much goodness 😄

I read in #725 how I can leverage something like useFormContext<MetaDataFormFields>() to have super strongly 💪 typed error.whatever.ayy.soNested?.message access which sounds amazing for less generic/universal components that are specific to one form type. Awesome!

I think I’m running into a similar type of issue (I’m upgrading from react-hook-form@3.28 to latest, haha). I had a reusable field component that uses FormContext and accesses a field’s errors similar to the examples in the docs

...
  const { errors, register } = useFormContext();
  const errorMessage = errors[name]?.message`
...

where name could be a nested value like "user.firstName". In v3 this worked just fine because the errors object was flat, and errors["user.firstName"]?.message was valid, however now I would need to access the property via errors.?user?.firstName?.message very explicitly, which I can’t generate dynamically 😢 (I don’t think, hopefully I’m just unaware of the fix 😅 ).

I’m able to work around this using lodash/get but I was very happy earlier being able to fully use dynamic properties + TS + optional chaining with the flat errors. Example workaround (very gross because of the type casting)

const errorMessage = (get(errors, name) as FieldError)?.message;

I absolutely love this library and it’s made things so much nicer than Redux-form / Formik, this is just a little nit since I feel dirty having to bring back lodash haha ❤️

Thanks @stern-shawn man it’s always a tradeoff…

to be honest, I love v3 flat errors, but then everyone are crying about the type-safe 😦 guess really i don’t have a choice rather than listen to the community, but 😃 i am going to export some of the utility functions from RHF, which include my tiny version of ‘get’ so you can skip using lodash if you want as well, (it’s on my todo list and it will come 😃

Hi @bluebill1049 thanks for the info. I cannot use optional chaining because I have written a custom error message component which receives the name (“aaa.bbb[0].xxx”) and errors object as props and renders default messages based on type if no message is specified in the instance.

I have solved it using a variation of this: https://stackoverflow.com/a/22129960/1524027 to parse the string and extract the error message.

I have discovered that errors object contains nested fields in a nested object structure, so

Wrong approach:

let err = errors[ 'prop[0].text' ]

Correct approach:

let err = errors['prop'] && errors['prop'][0] && errors['prop'][0]['text']

So it means that this is by design and not a bug. It is very confusing anyway because the name specified in the input component is a string (name=“prop[0].text”) and it is more logic to use the same string to find the error, but anyway. I think the documentation can be more explicit about this things.