Published on

How to Focus When an Error Occurs in React Hook Form Controller ?

Authors
  • Name
    Ripal & Zalak
    Twitter

How to Focus When an Error Occurs in React Hook Form Controller ?

React Hook Form (RHF) simplifies form handling in React applications. However, dealing with error focus on custom components wrapped by Controller can be tricky. This blog will show you how to correctly set focus when errors occur in custom components.

Problem

Using register in RHF, setting focus on fields with validation errors works seamlessly. However, with Controller, the focus doesn't automatically shift to the input with the error, especially for custom components with multiple fields. Here's how you can solve this issue.

Example Scenario

In the following example, a custom component CustomInput manages two fields (first and last) within an object. When an error occurs in either field, focus doesn't automatically shift to the problematic input.

import { Controller, useForm } from 'react-hook-form'
import CustomInput from './CustomInput'
import * as yup from 'yup'

const schema = yup.object({
  name: yup.object().shape({
    first: yup.string().required('First name is required'),
    last: yup.string().required('Last name is required'),
  }),
})

function Form() {
  const {
    handleSubmit,
    control,
    formState: { errors },
    setFocus,
  } = useForm({
    defaultValues: { name: { first: '', last: '' } },
    resolver: yupResolver(schema),
  })

  const onSubmit = (data) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="name"
        control={control}
        render={({ field }) => (
          <CustomInput value={field.value} onChange={field.onChange} errors={errors} />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  )
}

export default Form

Solution

To enable error focus for Controller-managed inputs:

1. Use setFocus and Watch for Errors

Use the setFocus method from useForm to programmatically focus on the first field with an error. Here's an example:

import React, { useEffect } from 'react'
import { useForm, Controller } from 'react-hook-form'

function Form() {
  const {
    handleSubmit,
    control,
    formState: { errors },
    setFocus,
  } = useForm({
    defaultValues: { name: { first: '', last: '' } },
  })

  useEffect(() => {
    if (errors.name) {
      if (errors.name.first) setFocus('name.first')
      else if (errors.name.last) setFocus('name.last')
    }
  }, [errors, setFocus])

  const onSubmit = (data) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="name"
        control={control}
        render={({ field }) => (
          <CustomInput value={field.value} onChange={field.onChange} errors={errors} />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  )
}

export default Form

2. Ref Prop for Custom Inputs

Ensure your custom component properly forwards the ref to the input element. For example:

function CustomInput({ value, onChange, errors, ref }) {
  return (
    <div>
      <input
        type="text"
        value={value.first}
        onChange={(e) => onChange({ ...value, first: e.target.value })}
        ref={(el) => (ref.current = { ...ref.current, first: el })}
      />
      <input
        type="text"
        value={value.last}
        onChange={(e) => onChange({ ...value, last: e.target.value })}
        ref={(el) => (ref.current = { ...ref.current, last: el })}
      />
      {errors.name && <p>{errors.name.first?.message || errors.name.last?.message}</p>}
    </div>
  )
}

3. Use inputRef for Material UI Components

If you're using Material UI, pass inputRef in place of ref:

<TextField
  {...field}
  inputRef={field.ref}
  error={!!errors.name?.first}
  helperText={errors.name?.first?.message}
/>

FAQs

Q: What happens if there are multiple errors?

Focus will shift to the first field with an error detected in the validation object. This behavior can be customized.

Q: Can this approach work with deeply nested fields?

Yes, by ensuring that the name property correctly maps to the field's path, such as name.first or address.city.

Q: Is this compatible with all component libraries?

Yes, as long as the custom component forwards ref or uses inputRef (for libraries like Material UI).

Conclusion

By leveraging setFocus and ensuring proper ref forwarding, you can handle error focus seamlessly with Controller in React Hook Form. This approach makes forms more user-friendly and enhances the overall experience.

If you found this guide helpful, share it with your network or leave a comment below!