Forms using hooks
With hooks, form processing is exactly the same as classes in terms of the overall methodology, but the syntax is slightly different as a result of the useState
hook.
Basic approach
Instead of using this.state
and this.setState
. We just have the useState
hook. But the controlled component principle is the same. Let’s say we have a simple email input:
const [email, setEmail] = useState("");
As this is a form, the state change is going to be the result of user input. So we need to prep our form to enable this.
<input type="text" value="{email}" onChange="{setEmail}" />
Now we just need to make good on the setEmail
method we declared when we initialised the state:
const handleChange = (event) => {
setEmail(event.target.value);
};
Applied example
Here is an applied example of the above approach for a form that has three input fields. This component outputs the inputs as JSON on submit:
function FormHook() {
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [age, setAge] = useState("");
const [formOutput, setFormOutput] = useState("Form output");
const handleSubmit = (event) => {
event.preventDefault();
setFormOutput(
JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={email} onChange={(event) => setEmail(event.target.value)}>
<input type="text" value={phone} onChange={(event) => setPhone(event.target.value)}>
<input type="number" value={age} onChange={(event) => setAge(event.target.value)}>
<button type="submit">Submit</button>
</form>
)
};
More complex forms
The above is fine if you only have one form with a couple of inputs. But if you are managing multiple forms or forms with a complex array of inputs, you would need to create useState
declaration for every single input with a custom onChange
event for each one which is repetitious and not very clean.
So instead of this, just like with class-based controlled components, we use the name
HTML attribute to distinguish each input and create a generic onChange
function that distinguishes each separate input by destructuring a key, value object using the name
.
<form onSubmit={handleSubmit}>
<input type="text" name="email" value={formValues.email} onChange={handleChange}>
<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
<input type="number" name="age" value={formValues.age} onChange={handleChange}>
<button type="submit">Submit</button>
</form>
const initialState = {
email: "",
phone: "",
age: "",
};
const [formValues, setFormValues] = useState(initialState);
const handleChange = (event) => {
const { name, value } = event.target;
setFormValues({ ...formValues, [name]: value });
};
There are three parts:
- First we create the initial state.
- Next we store this initial state as the variable in the
useState
initialisation:formValues
. We also provide a methodsetFormValues
which will be used by the change handler to log the user’s inputs. - Finally we create the function that will log the user changes. First we use object destructuring on the change event to enable us to retrieve the
name
andvalue
attributes of the HTML inputs in the component. Then we use spread syntax to say that for each input pair, retrieve its value, using the destructuredname
variable as the key.
Applied example
Below I have updated the previous context to this time reflect the new, abstracted logic:
function FormHookAbstracted() {
const initialState = {
email: "",
phone: "",
age: "",
};
const [formValues, setFormValues] = useState(initialState);
const handleChange = (event) => {
const { name, value } = event.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (event) => {
event.preventDefault();
setFormOutput(
JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="email" value={formValues.email} onChange={handleChange}>
<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
<input type="number" name="age" value={formValues.age} onChange={handleChange}>
<button type="submit">Submit</button>
</form>
)
};
export default FormHookAbstracted;
Note that instead of individual variables email
, phone
, age
, this approach returns a single object formValues
. We could therefore access the individual values with e.g [formValues.email](static/formvalues.email)
.
As it is an object, it makes resetting to the original state very easy, viz:
const handleReset = () => {
Object.values(formValues).map((x) => setFormValues(initialState));
};