This content originally appeared on DEV Community and was authored by Erik Lyngved
Cover image photo by Chris J. Davis on Unsplash
React Hook Form has quickly become my favorite library to wrangle forms of all shapes and sizes, mainly for its great developer experience. The 30 second screencast on their home page nicely illustrates how to integrate it into a standard form using the magic of register
to connect each field. When using native <input/>
components, it's pretty simple to get up and running.
But in the real world, we often don't work with vanilla inputs. Popular UI libraries often abstract and wrap any underlying form elements, making it hard or impossible to use with register
.
Sometimes we want to delight our users with a custom interactive component, like rating a product with 5 actual star icons instead of a boring select box. How can we connect these to an existing form without messy logic?
Enter the Controller
The library exports a <Controller/>
component which was made for exactly this purpose. It allows us to connect any component to our form, enabling it to display and set its value.
To use it, you'll need the control
object returned from useForm()
instead of register
. Also, as usual, you'll need a name
to tell the form which field we are controlling. Finally, the render
prop is where we place our component.
// Controller syntax
const { control } = useForm();
return (
<Controller
control={control}
name="myField"
render={/* Custom field component goes here */}
/>
);
Making the Field Component
Why is it called Controller
? It could be because our field component needs to be a controlled component.
In a nutshell, a controlled component is one that gets and sets its current "state" via props. In the case of a form field, that state is the field's current value.
<input/>
is one example of a component that can be controlled. We tell the input what its current value is, and we give it a way to tell us when that value should be changed.
// <input/> as a controlled component in a standard React form
const [val, setVal] = useState('')
return (
<input
type="text"
value={val}
onChange={e => setVal(e.target.value)}
/>
)
Here we see the two props required to make our field component work with the Controller:
-
value
- It should show the current value of the field. -
onChange
- It should be able to tell the Controller when a change to the current value is made.
These also happen to be two of the properties handed to us by the render
function! Its signature includes a field
object which has value
and onChange
(among other things).
It doesn't make much sense to use the Controller for a basic input, but here it is for illustration purposes:
// Using a basic input in a Controller
// (though you can just use `register` here)
const { control } = useForm();
return (
<>
<Controller
control={control}
name="myField"
render={({ field }) => (
<input {...field} />
// Equivalent to <input value={field.value} onChange={field.onChange} />
)}
/>
</>
)
Note: if you're using React Hook Form V6 or earlier, the function signature here is slightly different.
value
andonChange
are instead top-level properties of the argument, looking like the following instead.// V6 or earlier render=({ value, onChange }) => ( <input value={value} onChange={onChange} /> )
Real Examples
Using a UI library: Material UI
Check out the full example on Code Sandbox
Many projects use form inputs from popular UI libraries like Material UI. The problem is that any <input/>
components are usually hidden from us, so we can't use register
to connect them to our form. This is where Controller comes in!
Often they will use the same value
and onChange
props we're used to seeing.
If this is the case, we can simply spread the {...field}
object into the component.
// Using a Material-UI TextField component
<Controller
control={control}
name="myTextField"
render={({ field }) => <TextField {...field} />}
/>
Sometimes the props are not named the same. For example, Checkbox accepts its value as checked
instead of value
. This means we can't easily spread field
into it, but the result is still fairly easy to put together.
// Using a Material-UI Checkbox component
<Controller
control={control}
name="myCheckbox"
render={({ field: { value, onChange }}) => (
<Checkbox checked={value} onChange={onChange} />
)}
/>
Building from scratch: a five star rating field
Check out the full example on Code Sandbox
We've all probably used the ubiquitous widget that allows us to rate anything by clicking on a row of star icons. Thankfully, if we are just able to create a controlled component, we can cleanly fit it into the rest of the form.
Let's pretend we have a simple StarIcon
component that renders a single star icon. It accepts a active
boolean prop that tells it whether the icon should be filled in or not, and it can be clicked to fire an onClick
handler. If we use the value
and onChange
props as provided by the Controller's field
, we can construct a line of stars like so:
// Our controlled five star field component
const FiveStarField = ({ value, onChange }) => (
<>
<StarIcon active={value >= 1} onClick={() => onChange(1)} />
<StarIcon active={value >= 2} onClick={() => onChange(2)} />
<StarIcon active={value >= 3} onClick={() => onChange(3)} />
<StarIcon active={value >= 4} onClick={() => onChange(4)} />
<StarIcon active={value >= 5} onClick={() => onChange(5)} />
</>
)
Since we're using the usual props, we can simply pass this into the Controller with render={({ field }) => <FiveStarField {...field} />
.
Conclusion
Using <Controller/>
and a properly controlled component, you can make pretty much anything into a form field compatible with React Hook Form. The field can be as simple or fancy as you want, with any logic encapsulated in it, as long as it does these two things:
- Receive and render the current value/state of the field, commonly through the
value
prop. - Call a function when that value should be updated, commonly through the
onChange
prop.
This content originally appeared on DEV Community and was authored by Erik Lyngved
Erik Lyngved | Sciencx (2021-04-12T01:02:43+00:00) Turn Anything Into A Form Field With React Hook Form Controller. Retrieved from https://www.scien.cx/2021/04/12/turn-anything-into-a-form-field-with-react-hook-form-controller/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.