There are numerous ways to make a form input controlled from not controlled. The internet is mostly just a collection of forms ^_^ and one solution to make interacting with form inputs is to use the following approach...
Suppose we have a form with a single input field
<form>
<input type='text' id='username'/>
<button type='submit'>Send</button>
</form>
In the above example we are sending the field 'username' to our backend or to some end-point on submitting this form. To do this we must have two things a 'onSubmit' event and a way to keep track of the data entered into the input field of the form, one approach is...
const [value, setValue] = useState('')
<form onSubmit={handleFormSubmit}>
<input value={value} onChange={({target}) => setValue(target.value)} type='text' id='username'/>
<button type='submit'>Send</button>
</form>
The above snippet is making use of the 'useState' hook to make this component stateful. We are then passing the value and onChange event handler to the input field to keep track and store the value enetered into 'username'
This approach works fine and good but we will have to define multiple 'onChange' event handlers if our form gets more input fields and as the complexity grows the components code will start to look messy.
One thing we can use here is custom hooks, that is we create a function that utilises the react-hooks and customise its behaviour to suit our needs. One approach to do this is like so...
import {useState} from 'react'
export const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
const reset = () => {
setValue('')
}
return {
type,
value,
onChange,
reset
}
}
Now we can import this custom hook into any component where we want to use input fields, like so...
import useField from '..file its being exported from'
Then we can use it in our react component, like so..
const {reset, ...username} = useField('text')
<form onSubmit={handleFormSubmit}>
<input {...username} type='text' id='username'/>
<button type='submit'>Send</button>
</form>
Now one way to implement the handleFormSubmit
is like so...
const handleFormSubmit = (event) => {
event.preventDefault()
let userObject = {
username: username.value
}
// send data to backend
reset()
}
We can call the reset method after successfully sending form-data to the backend and the input field is reset to an empty string.
Using this approach we make the code much cleaner and dont need to use multiple useState hooks within our components which clutters the code.
Try this in your next form in React! ^_^
Top comments (10)
So, if I needed another input I would just do this? {reset, ...username, ...age} = useField('text').
I didn't get it, how would I avoid multiple useStates with this.
Sorry, I'm new to React, coming from Angular and VueJS.
Great article BTW. Thanks
Well it's not just you, I also fail to grasp how this works. The idea seems nice, but the author could have elaborated just a tiny little bit more for the mere mortals among us.
Ah on reading it a second time I get it now ... for a second field (let's say "email") you'd just do:
so for those 2 fields you'd have (we need to "rename" the 'reset' member during destructuring):
and so on ... the only thing I don't get is why we need the "type" parameter to the hook:
useField('text')
... and we probably also don't need the "id" attributes (id='username'
andid='email'
).So, this might work equally well (and save some typing) ... ?
Other than that, yeah pretty clever and elegant.
I came to the same conclusion, but if this is true, then now I have multiple useFields instead of useStates, it is just omitting value and onChange. About the Type parameter, I think he wanted to also set the input type.
Yes you'd have multiple useFields, I think that's clearly the idea.
But it's not omitting value and onChange - instead, value and onChange are being expanded as props on the elements, so you're effectively linking the attributes on the input elements with function closures which are generated by the "useField" calls.
I still don't see the point of the type param, unless you'd end up with just:
where the 'type' attribute is then also generated through the object expansion (the ... ellipsis).
Yeah absolutely, we need not have multiple onChange handlers either. The type parameter is for the input element's type attribute is required to mention what type of input it is, could be email, password, text, number, range etc.
So instead of repeated useStates we have repeated hook definings,
What ever you do, forms are painfull in react,
The most clean approach is to put all of elements in a single useState object, and access them by object notation like 'form[e.target.name] = e.target.value' but with the cost of rerendering the whole form in every input value change. I actually do it if there's only 2 or 3 inputs.
Clever and elegant technique, love it, but why does the hook need a "type" parameter? Looking at the code I have no clue what's that used for TBH.
You could use the type param to derive the value, as not all input types will get value from target. Also allows you to just spread the
rest
into the input props with no additions.I like this simple elegant solution. I'm a
react-hook-form
fan myself but this is great if you don't need anything too complex.Ah right, got it ... yes then I see how 'type' could be useful.