12
Handling Forms in React
Form is an essential and basic part of any Modern Web Application. It allows user to interact with the application as well as give their data. Forms can be used to perform various actions as per the nature of business requirement and It can be used anywhere like user authentication, adding/updating user, filtering, ordering etc. It has various type of elements to collect the information based on the type like, input, textarea, select, button etc.
Well, there are many front end frameworks each have it own way of handling forms. In Angular, There are two ways of handling like Template Driven and Reactive Forms.
When it comes to React, there are two ways of handling in react too as following,
- Uncontrolled Components We depend on DOM elements by attaching refs to it. Data fetching from the elements and validation becomes difficult and hard to manage.
- Controlled Components Unlike Uncontrolled Components, We maintain complete data as/in state variable. It is easy to maintain and most followed practice and recommended one in React Community.
As React is not complete framework like angular. For handling forms React, either we need to write our own logic or depend on any third party package like React Hook Form, Formik.
Here is an example of basic React form written with minimal bootstrap CSS and no other libraries were included.
In the example shown above, first we initialize required fields - email
and password
with necessary properties - valid
, touched
, error
and touched
in the state using useState
hook .
const [formData, setFormData] = useState({
email: { valid: true, value: "", error: "", touched: false },
password: { valid: true, value: "", error: "", touched: false }
});
Next, we return JSX with form wrapped around with some elements, elements are bound with some events to handle the changes in the form.
<div className="row" style={{ marginTop: "100px" }}>
<div className="col-md-12">
<h4 className="text-center">Sign In</h4>
<form className="form-horizontal" onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="email" className="col-sm-2 control-label"> Email </label>
<div className="col-sm-10">
<input type="email" className="form-control" id="email" placeholder="Email" value={email.value} onChange={handleChange} name="email" autoComplete="off" />
<label className="label label-danger" style={{ display: email.touched && email.error ? "inline" : "none" }}> {email.error} </label>
</div>
</div>
<div className="form-group">
<label htmlFor="password" className="col-sm-2 control-label"> Password</label>
<div className="col-sm-10">
<input type="password" className="form-control" id="password" placeholder="Password" value={password.value} onChange={handleChange} name="password" />
<label className="label label-danger" style={{ display: password.touched && password.error ? "inline" : "none" }} > {password.error} </label>
</div>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="submit" className="btn btn-default">Sign in</button>
</div>
</div>
</form>
</div>
</div>
Now, we write handleChange
method to handle the changes in the state with the user input.
const handleChange = (e) => {
const target = e.target;
const { name, value } = target;
let prevFormData = { ...formData };
let error = validateFormField(name, value);
prevFormData[name] = {
...prevFormData[name],
value: value.trim(),
touched: true,
error
};
setFormData(prevFormData);
};
Anytime values in the state updates, we run the validations against the input - using validateFormField
method. Here in this method, we check for multiple conditions and returns a respective error message.
const validateFormField = (name, value) => {
let error = "";
if (name === "email") {
if (!value) {
error = "email is mandatory";
}
if (value) {
if (!/^\w+([\\.-]?\w+)*@\w+([\\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
error = "give a valid email";
}
}
}
if (name === "password") {
if (!value) {
error = "password is mandatory";
}
if (value) {
if (value.length < 3) {
error = "password should be atleast 3 characters";
}
if (value.length > 10) {
error = "password should not exceed 10 characters";
}
}
}
return error;
};
And, we left with the final step i.e., handling submit. We are handling form submission process with onSubmit
method. We need to check for the validity of the form, so as to do this, we need call validateFormField
looping through the state.
const onSubmit = (event) => {
event.preventDefault();
let prevFormData = { ...formData };
let error;
let isValid = true;
for (let field in prevFormData) {
error = validateFormField(field, prevFormData[field]["value"]);
if (isValid && error) {
isValid = false;
}
prevFormData[field] = {
...prevFormData[field],
touched: true,
error
};
}
if (!isValid) {
setFormData(prevFormData);
} else {
alert(JSON.stringify(prevFormData));
}
};
Wow, finally we made it. Form is now ready to use. But, it is not that funny right? Yes, creating forms in React is not much funny. It need a lot of code base. So far we have written for only two text boxes, imagine if the form has at most 10 fields.
It is weird right? creating form, handling state management and validation status of 10 fields will become nightmare, boring as well. If we create a form in plain "React", we would do the following, at a minimum,
- Set up the state for the form values, form errors and validation status.
- Handling user input and state changes.
- Method for validating the inputs.
- Handling form submission method.
Formik is the library that standardize the input component and controls the flow of form submission. It comes with state management and handling user input changes. Formik helps you to write the following parts of building the form:
- Validation and error messages
- Handling form submission process.
- Get values in and out of the form state
Here again the same example, but with Formik -
Formik uses a design pattern - Render Callback. We pass an anonymous function as child element for Fomik, It will get called at some point of time from the Formik.
<Formik>
{(props)=>{
return (
/* form comes here */
)
}}
</Formik>
Let's see how Formik will make build React form easier.
Formik will need basic configuration as -
- Initial values
- Validation handler
- Submit handler
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={(values) => { /* submit handler */ }}
validate={(values)=>{/* validation handler */}} >
{(props) => {
return (
<form className="form-horizontal" onSubmit={props.handleSubmit}>
/* form elements come here */
</form>
);
}}
</Formik>
With the above minimal configuration, Formik will setup its own state, so we no need to maintain state and handle it. It will take care of. 😎
Validation in Formik will be executed automatically at 3 phases - input change, focus out of an particular input field and when user submits the form. Don't worry about them. All wee need to do is, simply pass a plain JavaScript function to Fomik's validate
prop.
Compare validation in vanilla React vs Formik
// vanilla React validation code
const handleChange = (e) => {
const target = e.target;
const {
name,
value
} = target;
let prevFormData = {
...formData
};
let error = validateFormField(name, value);
prevFormData[name] = {
...prevFormData[name],
value: value.trim(),
touched: true,
error
};
setFormData(prevFormData);
};
const validateFormField = (name, value) => {
let error = "";
if (name === "email") {
if (!value) {
error = "email is mandatory";
}
if (value) {
if (!/^\w+([\\.-]?\w+)*@\w+([\\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
error = "give a valid email";
}
}
}
if (name === "password") {
if (!value) {
error = "password is mandatory";
}
if (value) {
if (value.length < 3) {
error = "password should be atleast 3 characters";
}
if (value.length > 10) {
error = "password should not exceed 10 characters";
}
}
}
return error;
};
// Formik Validation
validate = {values => {
let errors = {};
if (!values.hasOwnProperty('email')) {
error = "email is mandatory";
} else {
if (!/^\w+([\\.-]?\w+)*@\w+([\\.-]?\w+)*(\.\w{2,3})+$/.test(values.email)) {
error = "give a valid email";
}
}
if (!values.hasOwnProperty('password')) {
error = "password is mandatory";
}else {
if (values.email < 3) {
error = "password should be atleast 3 characters";
}
if (values.email > 10) {
error = "password should not exceed 10 characters";
}
}
return errors;
}
}
With the validation in place, now we need to output the error messages as our own way.
Here, In the above example we have used one React hook - useValidator
. We extracted the validation logic and put it in one hook 😎
const useValidator = (validationsInfo) => {
return function (values) {
let errors = {};
for (let key in validationsInfo) {
let fieldValitions = validationsInfo[key];
let fieldValue = values[key];
for (let validationObejct of fieldValitions) {
let { type, value, message } = validationObejct;
let isValid = true;
switch (type) {
case "required":
if (value === true) {
if (!fieldValue && fieldValue !== 0) {
isValid = false;
}
}
break;
case "regex":
try {
const validationRegex = new RegExp(value, "g");
isValid = validationRegex.test(fieldValue) === true;
} catch (error) {}
break;
case "minlength":
if (fieldValue.length < value) {
isValid = false;
}
break;
case "maxlength":
if (fieldValue.length > value) {
isValid = false;
}
break;
case "exactlength":
if (fieldValue.length !== value) {
isValid = false;
break;
}
break;
case "minvalue":
fieldValue = +fieldValue;
if (fieldValue < value) {
isValid = false;
}
break;
case "maxvalue":
fieldValue = +fieldValue;
if (fieldValue > value) {
isValid = false;
}
break;
case "custom":
try {
if (typeof value === "function") {
isValid = value(fieldValue) === true;
}
} catch (error) {}
break;
default:
break;
}
if (!isValid) {
errors[key] = message;
break;
}
}
}
return errors;
};
};
export default useValidator;
We need to pass JavaScript object with the validations -Regex
, minlength
, maxlength
etc. Even we can execute a function using custom
.
const validator = useValidator({
email: [
{
type: "required",
value: true,
message: "email is required"
},
{
type: "regex",
value:
"^([a-zA-Z0-9_\\.\\-])+\\@(([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})+$",
message: "enter valid email"
}
],
password: [
{ type: "required", value: true, message: "password is required" },
{
type: "minlength",
value: 2,
message: "password should be minimum two length"
},
{
type: "custom",
value: function (val) {
if (typeof val !== "string") {
val = String(val);
}
if (val.length > 10) {
return false;
}
return true;
},
message: "password should not exceed 10 characters"
}
]
});
The useValidator
hook will return a function, that we need to pass it to Formik with validate
prop.
Building forms is one of those things that React isn’t good at. Luckily, React has a community of developers that help each other and make the process of writing code easier.
Formik is definitely one of those open source libraries that’s a must-have if you are writing many forms in your React application.
That is it. Keep Learning.
12