How to use React Aria TextField with React Hook Form
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!