2 min read

How to use React Aria TextField with React Hook Form

How to use React Aria TextField with React Hook Form
Photo by Agata Create / Unsplash

React Aria has a slightly different interface for some of its properties than the native DOM exposed by React. They do this so the library is more consistent and can be used cross-platform.

This, most of the time, works great. When you want to build your own complex component, you don't need to expose events in your change handlers, you can just expose the value.

However, some libraries expect a certain interface.

React Hook Form has a wonderful useForm hook that is used to wire up entire forms easily. It returns a register function that is passed to your inputs to automatically wire up the inputs.

For example:

import { useForm } from 'react-hook-form'

const Form = () => {
  const {
      register,
      handleSubmit
    } = useForm()

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

    return (
      <form onSubmit={handleSubmit(onSubmit)}>
        <input {...register('exampleRequired', { required: true })} />
        <button type="submit">Submit</button>
      </form>
    )
}

The above code ensures the input field will have its data captured. The library also marks it as required, so the user must fill it out.

This works because the register method returns the value and onChange handers that are passed to the input.

This won't work for React Aria. The onChange handler is an event handler with an interface:

onChange(event: SyntheticEvent) => void

To get the underlying value, you must do, event.target.value. Something I'm sure you're used to seeing in a lot of React code.

React Aria doesn't pass the event back to you, though. It calls event.target.value for you and passes back just the string. This makes their interfaces consistent but means sometimes it won't play nice with other libraries.

To fix it, you can use the useController hook from React Hook Form. Create a simple wrapper around the TextField you want to manage:

import { TextField } from "react-aria-components"
import { useController, useForm } from "react-hook-form"

function FormTextField({ control, name, label }) {
  const {
    field,
    fieldState: { invalid, isTouched, isDirty },
    formState: { touchedFields, dirtyFields }
  } = useController({
    name,
    control,
    rules: { required: true },
  });

  return (
    <TextField 
      label={label}
      onChange={field.onChange} // send value to hook form 
      onBlur={field.onBlur} // notify when input is touched/blur
      value={field.value} // input value
      name={field.name} // send down the input name
      ref={field.ref} // send input ref, so we can focus on input when error appear
    />
  );
}

And then you can use it in your forms:

import { useForm } from 'react-hook-form'
import { FormTextField } from './form-text-field'

const Form = () => {
    const {
      control
      handleSubmit
    } = useForm()

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

    return (
      <form onSubmit={handleSubmit(onSubmit)}>
        <FormTextField control={control} name="email" label="Email" />
        <button type="submit">Submit</button>
      </form>
    )
}

Happy coding!