Building forms with React involves setting up state as the container for user data and props as the means to control how state is updated using user input. Validation can be done in between user inputs, and an arbitrary submit function is executed on form submit.
Here is an example of a basic React form written without libraries and with minimal Bootstrap styling:
In the example below, we first initialize required state values in the constructor
method. Since we have two required inputs — email
and password
— we initialize state for input values, input validity, and input errors:
constructor(props) {
super(props);
this.state = {
formValues: {
email: "",
password: ""
},
formErrors: {
email: "",
password: ""
},
formValidity: {
email: false,
password: false
},
isSubmitting: false
};
}
Next, we create the render method of the form with input values derived from state:
render() {
const { formValues, formErrors, isSubmitting } = this.state;
return (
<div className="container">
<div className="row mb-5">
<div className="col-lg-12 text-center">
<h1 className="mt-5">Login Form</h1>
</div>
</div>
<div className="row">
<div className="col-lg-12">
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>Email address</label>
<input
type="email"
name="email"
className={`form-control ${
formErrors.email ? "is-invalid" : ""
}`}
placeholder="Enter email"
onChange={this.handleChange}
value={formValues.email}
/>
<div className="invalid-feedback">{formErrors.email}</div>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
name="password"
className={`form-control ${
formErrors.password ? "is-invalid" : ""
}`}
placeholder="Password"
onChange={this.handleChange}
value={formValues.password}
/>
<div className="invalid-feedback">{formErrors.password}</div>
</div>
<button
type="submit"
className="btn btn-primary btn-block"
disabled={isSubmitting}
>
{isSubmitting ? "Please wait..." : "Submit"}
</button>
</form>
</div>
</div>
</div>
);
}
Now we need to write the handleChange
method to update the state with user inputs:
handleChange = ({ target }) => {
const { formValues } = this.state;
formValues[target.name] = target.value;
this.setState({ formValues });
this.handleValidation(target);
};
Anytime the state values are updated, we’ll run a validation method against user inputs. This is our handleValidation
method:
handleValidation = target => {
const { name, value } = target;
const fieldValidationErrors = this.state.formErrors;
const validity = this.state.formValidity;
const isEmail = name === "email";
const isPassword = name === "password";
const emailTest = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
validity[name] = value.length > 0;
fieldValidationErrors[name] = validity[name]
? ""
: `${name} is required and cannot be empty`;
if (validity[name]) {
if (isEmail) {
validity[name] = emailTest.test(value);
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be a valid email address`;
}
if (isPassword) {
validity[name] = value.length >= 3;
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be 3 characters minimum`;
}
}
this.setState({
formErrors: fieldValidationErrors,
formValidity: validity
});
};
The last part of this basic form is a handleSubmit
method for the submission process. We need to check on formValidity
values, and if there are any false
values, run the validation method again without submitting the form.
handleSubmit = event => {
event.preventDefault();
this.setState({ isSubmitting: true });
const { formValues, formValidity } = this.state;
if (Object.values(formValidity).every(Boolean)) {
alert("Form is validated! Submitting the form...");
this.setState({ isSubmitting: false });
} else {
for (let key in formValues) {
let target = {
name: key,
value: formValues[key]
};
this.handleValidation(target);
}
this.setState({ isSubmitting: false });
}
};
Now the form is ready for use. React only provides the “view” layer for your application, and that means it provides only the basic necessities in making form components. component
, state
, and props
are like puzzle blocks that you have to piece together to build a working form.
As you can see, it’s quite a lot of code for a form with only two text boxes. Imagine how many state values you need to keep track of in a form with 10 inputs or more. Yikes!
Yes, making forms with React is no fun; it’s very verbose and rigid. Building the form and creating validation method are boring tasks. In each form, you’d need to do the following, at a minimum:
- Set up state for form values, form errors, and form validity
- Handling user inputs and updating state
- Creating validation functions
- Handling submission
Building forms the natural “React” way requires you to write every part of the process from setting up states to form submission. I have done countless React forms, and I always find this part of building forms very boring and time-consuming. Fortunately, I’m not the only one feeling that way.
Enter Formik
Jared Palmer authored the Formik library out of frustration when building React forms. He needed a way to standardize the input components and the flow of form submission. Formik helps you to write the three most annoying parts of building a form:
- Getting values in and out of form state
- Validation and error messages
- Handling form submission
Here is the same form again, but this time using Formik:
This new form only uses four extra components from Formik library: <Formik />
, <Form />
, <Field />
, and <ErrorMessage />
. In order to unlock Formik’s power, you can wrap your form inside the <Formik />
component:
<Formik>
<Form>
{/* the rest of the code here */}
</Form>
</Formik>
Let’s see how Formik makes building forms easier compared to React’s natural way.
Getting values in and out of form state
Formik will set up state internally for storing user inputs through its initialValues
prop, so you don’t need to initialize state from constructor anymore.
In order to get values in and out of Formik internal state, you can use the <Field />
component to replace the regular HTML <input />
component. This component will do the magic of keeping Formik state and input value in sync, so you don’t have to pass value
and onChange
props into the <Field />
component:
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={({ setSubmitting }) => {
alert("Form is validated! Submitting the form...");
setSubmitting(false);
}}
>
{() => (
<Form>
<div className="form-group">
<label htmlFor="email">Email</label>
<Field
type="email"
name="email"
className="form-control"
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Field
type="password"
name="password"
className="form-control"
/>
</div>
</Form>
)}
</Formik>
With Formik, there’s no need to initialize state in constructor
and create your own handleChange
method anymore. It’s all taken care of.
Validation and error messages
Validation in Formik is executed automatically during specific events. All common events like after user input, on focus change, and on submit are covered, and you don’t have to worry about them. All you need to do is pass a function into Formik’s validate
prop.
Compare this code between Formik validation and vanilla React validation:
// Formik validation code. Take values from Formik
validate={values => {
let errors = {};
if (values.email === "") {
errors.email = "Email is required";
} else if (!emailTest.test(values.email)) {
errors.email = "Invalid email address format";
}
if (values.password === "") {
errors.password = "Password is required";
} else if (values.password.length < 3) {
errors.password = "Password must be 3 characters at minimum";
}
return errors;
}}
// Vanilla React validation code. Take values given by handleChange
handleValidation = target => {
const { name, value } = target;
const fieldValidationErrors = this.state.formErrors;
const validity = this.state.formValidity;
const isEmail = name === "email";
const isPassword = name === "password";
const emailTest = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
validity[name] = value.length > 0;
fieldValidationErrors[name] = validity[name]
? ""
: `${name} is required and cannot be empty`;
if (validity[name]) {
if (isEmail) {
validity[name] = emailTest.test(value);
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be a valid email address`;
}
if (isPassword) {
validity[name] = value.length >= 3;
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be 3 characters minimum`;
}
}
this.setState({
formErrors: fieldValidationErrors,
formValidity: validity
});
};
With validation in place, now you need to output error messages. Formik’s <ErrorMessage />
component will automatically display error message for the <Field />
component with the given name. You can adjust what HTML tag will be displayed through the component
prop. Since this example form is using Bootstrap’s style, you will have to add a className
prop as well:
// Formik error message output
<Field
type="email"
name="email"
className={`form-control ${
touched.email && errors.email ? "is-invalid" : ""
}`}
/>
<ErrorMessage
component="div"
name="email"
className="invalid-feedback"
/>
// Vanilla React error message output
<input
type="email"
name="email"
className={`form-control ${
formErrors.email ? "is-invalid" : ""
}`}
placeholder="Enter email"
onChange={this.handleChange}
value={formValues.email}
/>
<div className="invalid-feedback">{formErrors.email}</div>
The code for error message is actually about the same, but there’s a lot less code in Formik’s validation than in vanilla React. Way to go, Formik!
Even easier validation with Yup
Although you can already feel the benefit of using Formik in validation process, you can make it even easier by using an object schema validator.
An object schema validator is simply a library that allows you to define the blueprint of a JavaScript object and ensure that the object values match that blueprint through the validation process. This is particularly useful in validating form data since it’s actually an object kept inside Formik’s values
prop.
Now one such library is Yup, and Formik’s author loves Yup so much that he included a special prop that connects Yup with Formik called validationSchema
. This prop will automatically transform Yup’s validation errors into a pretty object whose keys match values
and touched
.
Here is an example of Formik using Yup as its validation schema. Notice how the validate prop is removed from the <Formik />
component:
With Yup’s object schema validator on place, you don’t have to manually write if
conditions anymore. You can learn more about Yup and what kind of validation it can do by visiting its GitHub repo.
Form submission process
Formik’s <Form />
component will automatically run your validation method and cancel the submission process if there are any errors. While you have to include the onSubmit prop to a regular <form />
element, Formik’s <Form />
wrapper will run the onSubmit
prop function you passed into the <Formik />
component:
// Formik's submit code. Won't be executed if there are any errors.
onSubmit={({ setSubmitting }) => {
alert("Form is validated!");
setSubmitting(false);
}}
// Vanilla React submit code. Check on validity state then run validation manually.
handleSubmit = event => {
event.preventDefault();
this.setState({ isSubmitting: true });
const { formValues, formValidity } = this.state;
if (Object.values(formValidity).every(Boolean)) {
alert("Form is validated!");
this.setState({ isSubmitting: false });
} else {
for (let key in formValues) {
let target = {
name: key,
value: formValues[key]
};
this.handleValidation(target);
}
this.setState({ isSubmitting: false });
}
};
Formik requires only four lines of code for submission at minimum, and you don’t need to keep track of the validity of form inputs. That’s pretty neat!
But what about redux-form?
Sure, redux-form works great, but then you’d need to use Redux in the first place. What if you’re using MobX? What if a new, better library comes up in the future and you want to replace Redux with that? On top of all that, does your React form actually affects the flow of your entire application in some way?
Think about it: Does the value of the username textbox somehow matter to your application globally? If not, then it’s really not necessary to track its value using Redux. Even the prophet Dan Abramov said the same thing.
Another problem with redux-form is that you are storing form input values into Redux store. This means your application will call on Redux’s reducer on every keystroke to update the value of just one textbox. Not a good idea.
I love writing forms the “Formik” way, but if you prefer redux-form, then that’s fine, too. 😉
Conclusion
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. It really speeds up your development process and reduces boilerplate code by abstracting away parts of your form through components like <Field />
and <Form />
.
While a vanilla React form requires you to specify your own state values and methods, you can simply pass props to the <Formik />
component to do the same things: handle user inputs, validate inputs, and form submission.
If you’d like to learn more about Formik, head over to the documentation or watch the presentation below by its creator.
React Distilled 2.0 is released
If you'd like to learn more about React and how you can use it to build a complete web application from scratch, I'm offering a 28% off my book React Distilled to celebrate its release (from $49 to $34).
It includes new chapters on React Context API and React Hooks, and it shows how you can create React app using nothing but React and Firestore.
Go grab it now so you can be a React Genius today!
Originally published at https://blog.logrocket.com on June 28, 2019.
Top comments (0)