Tired of slow React forms? Try this.
Since React's inception- the classic "state 101" tutorial/example has looked like this:
function myComponent(){
[fieldValue, setFieldValue] = useState('');
return <div>
<input onChange={({target})=>setFieldValue(target.value)}/>
</div>
}
People get carried away with this example... Many use it years later- until they hit a complex form with 20 fields that won't scale. In many cases, it's a slow solution. Funny thing is: if we think more in line with vanilla JavaScript and HTML, we find a more performant answer with better a11ty to boot.
What's wrong with using state for forms?
To understand why this can be a sub-optimal way of dealing with inputs, we need to understand React's life cycle.
Whenever you use state in React, it will observe it. Whenever the state changes, React will:
-
Check for any props based state changes via
getDerrivedStateFromProps -
Run the
shouldComponentUpdatefunction -
Render the changes via the
renderfunction-
This involves the Virtual DOM
-
The Virtual DOM is a copy of the DOM used to compare which components need changing before rendering
-
This saves on performance, as React only updates elements that need changing
-
-
Then,
getSnapshotBeforeUpdategets called, providing the previous props and state before the DOM is altered reflecting the changes with the new state. -
Now, DOM gets updated with all the changes.
-
Finally, the
componentDidUpdatefunction gets called.
Now think: if you're using the onChange event listener on multiple inputs across a whole form, imagine how intense that is. All 6 of those steps are running for each key stroke on every input!
No shame though. I've done even worse onChange crimes than this (think a singular function that sets the state of an object based in input names)! There are brighter skies ahead.
So what do we do instead?
I'm so glad you asked: we gotta think "old school"!
In the age of the SPA web, it's easy to forget that forms have the onSubmit event listener. Let's take a look:
function myComponent(){
const handleForm = (event)=>{
event.preventDefault()
const inputValue = event.target.inputName.value;
event.target.clear();
}
return <form onSubmit={handleForm}>
<input name="inputName" defaultValue="This can also be a variable!" required/>
<input type="submit"/>
</form>
}
Here we take advantage of the handleForm event listener, this a function which only gets called on submission. This approach has several advantages:
-
No expensive 6 function lifecycle operations on every key stroke
-
No need for passing the
onChangeprop to each input, onlynameattributes needed. -
Easy input value retrieval by referencing
event.target.inputName.value. -
The ability to clear all fields in the onSubmit event listener via
event.target.clear(), no state binding required! -
Better accessibility due to more descriptive DOM (using the form element makes it easier for screen readers to navigate your page)
How do we initialize fields with values on load? With the simple but elegant defaultValue HTML attribute. No need for onChange and value state binding here!
What about forms with many fields?
I like to loop through the elements object in the onSubmit handler:
(event) => {
event.preventDefault()
const elements = event.target.elements;
const elementKeys = Object.keys(elements);
let keyValues = {}
elementKeys.forEach(key => {
const element = elements[key]
if (element.name)
keyValues[element.name] = element.value;
})
}
This will go through each input in your form, and put a key value pair into an object based on the name and value of your input; perfect for sending as JSON to your REST API!
Obviously, this won't be the perfect approach in all scenarios, but it certainly fits most the ones I've come across. It saves a tonne on performance and is much cleaner to work with too. The opportunities are endless. Enjoy!