So, you just got started with React and you’ve started building React apps and you’re wondering, how do I work with forms in React? Fret not.
In this tutorial, I will explain how React deals with forms and events. I’ll also walk you through how to write code that helps to get input from the different form components and submit the data in a React application.
Finally, I’ll show how to use the Formik library to build all kinds of forms, from simple to complex.
Forms and events in React
Before we begin to write code, it’s important to note how React deals with forms and events. Consider this simple input
tag below:
<input type="text" placeholder="First Name">
In order to get the data inside the input tag, you need to be able to fetch the content somehow. This doesn’t come easy for React, as there’s no preset API to help with this like Vue.js’ v-model
or Angular’s ng-model
.
It doesn’t come easy, but it can be done. How?
By making sure that the view (the input, select, or textarea field) is always in sync with the state. This means that the value for the input element must first be created in the state and then set to that state value in the render()
function.
Something like the code block below:
As you can see in the code block above, the input
element has a value of this.state.inputvalue
which means the value of the input element is set to be in sync with the inputvalue
in the state. This essentially translates to the fact that whatever is typed in the input field will be stored in the state, but there’s a problem here.
React doesn’t know how to detect if there’s a change in the input element if there was an addition or deletion. Use of a form event handler — onChange
to check for changes.
This will lead to the code block below:
In the code block above, the input element now has an addition to the onChange
event. The onChange
event will be executed whenever there’s a change in input element and it’s set to execute the handleChange()
function.
The handleChange()
function will always automatically set the state to the current value of the input. One more thing to note is the addition of a new line of code inside the constructor.
this.handleChange = this.handleChange.bind(this);
The line of code above is used to bind the handleChange()
function. This is done because in JavaScript, class methods are not bound by default. If this is not done, the handleChange()
function will return undefined when the onChange
event is called.
So React knows how to store the values from the input element to the state now, but how do we deal with form submission. Very simple. Take a look at the code block below:
This is essentially the same code block as seen above, but with a few additions. The form now has an onSubmit
event that executes the handleSubmit
function.
The handleSubmit()
function does two things; it logs the current value of the input element whenever the form is submitted and most importantly it prevents the default HTML form behavior of browsing to a new page. The line of code below is also added to the constructor so as to bind the handleSubmit function.
this.handleSubmit = this.handleSubmit.bind(this);
It’s important to note that there are different methods to binding the functions. Binding them in the constructor is one way, but we can also use fat arrows.
See the code block below for reference:
The syntax above makes sure that this is bound within the handleSubmit
function.
The whole process above of syncing the value of the input element to the state is called Controlled Components. We essentially made the React state the single source of truth. The React component that’s responsible for rendering the form is also responsible for what happens whenever a user adds anything to the form.
Now that we’ve seen how React deals with forms and how to ensure that the values are in sync with the state, let’s build a proper form with different types of fields, that is, <input>
, <textarea>
, <select>
, radio
e.t.c
The code block/demo below has all the code needed to demonstrate how to deal with the different form fields. We’ll be going through each of them to see how it works.
Input
The code implementation for the input
field is straightforward. The value is set to be in sync with fullname
which is already declared in the state. The onChange
event is used to execute the handleChange
function if there’s a change in the input
field.
The only new thing on the input
field above is the addition of the name
attribute. Because we’ll be dealing with multiple input fields, it’s important to note which of them is actually being modified and the name
attribute helps with that. The value of the name
attribute has to be the same with the state values declared in the constructor.
This is also why there’s a change in the handleChange
function.
In the code block above, the handleChange
function uses the name
attribute that was assigned to the different input
elements to determine what to do based on the value of the event.target.value
Textarea
The textarea
field also works in a similar fashion to that of the input
field. The value is set to be in sync with message
which is already declared in the state. It also has the name
attribute and it’s set to message
.
Select
The select
element has a value
attribute that’s set to be in sync with editor
which is already declared in the state. Because it’s a dropdown of options, it’s important to know what’s being selected, that’s why each of the option tags has it’s own value
attribute with unique contents.
Checkbox
The implementation of the checkbox
element in React forms is a bit different from the others above. Instead of setting the this.state.terms
value to the value
attribute on the input field, it’s set to the checked
attribute. The state value also has to be a boolean value, that means either a truthy or falsy value.
Radio
Implementing the radio
element in React forms works in a similar fashion to that of the checkbox above. The radio
elements all have the same name attribute but with different value
attributes, as seen above where the value for the Yes radio is Yes and the value for the No radio is No. The checked attribute is used to set the value of the state to either Yes or No as seen above whenever either of the two is selected.
Using Formik
If you think setting up React forms in the manner above is a bit stressful and worrisome, then I have good news for you. The Formik library helps to make powerful and lightweight forms in React. It gives you the ability to grab and manipulate values, set errors and warnings, customize inputs and many more features that make building forms easy.
Formik keeps track of your form’s state and then exposes it plus a few reusable methods and event handlers (handleChange, handleBlur, and handleSubmit) to your form via props. handleChange and handleBlur work exactly as expected — they use a name or id attribute to figure out which field to update. — https://github.com/jaredpalmer/formik
The Formik component in conjunction with Yup can be used to write awesome form validations. Yup is used for object schema validation and that means it can be used as a validator for when building React forms with formik.
When it comes to the formik API, there are three important APIs to consider and understand:
- withFormik(options)
- <Field />
- <Form />
withFormik(options)
The withFormik(options)
allows you to create a higher-order React component class. You can then pass in some props and form handlers in the component based on the supplied options. Let’s take a look at some of the available options that can be in withFormik
handleSubmit
handleSubmit
as the name suggests, helps with the form submission in formik. It is automatically passed the form values and any other props wrapped in the component.
mapPropsToValues
mapPropsToValues
is used to initialise the values of the form state. Formik transfers the results of mapPropsToValues
into updatable form state and makes these values available to the new component as props.values.
validationSchema
This could a function that returns a Yup Schema or an actual Yup schema itself. This helps with validation inside the form.
<Field />
The Field
component in Formik is used to automatically set up React forms with Formik. It’s able to get the value by using the name
attribute, it uses the name
attribute to match up the Formik state and it always set to the input
element. That can easily be changed by specifying a component prop.
<Form />
<Form/>
is a helper component that helps to save time. It helps to prevent typing out <form onSubmit={props.handleSubmit}/>
. All you have to do is wrap in all the form details in a <Form></Form>
tag, like the code below:
With those basics understood, let’s see how Formik can be used to build and validate a React form.
Like the controlled components above, a full-fledged form will be built and we’ll then go over the different parts of the form and how it works. The entire code/demo can be viewed with the link below.
In the code block above, the first lines of code are imports. We import React
, render
from react-dom
, some components from formik
with a destructuring assignment. Yup which is a JavaScript object schema validator is also imported.
The next block of code is the fat arrow App
function with a param of props
. The props
param is set to an object that contains values that will be used in the app.
All of the details gotten from the form is stored in values
, validation errors are stored in errors
, touched
is a boolean value that checks if an input field is in focus, handleChange
helps to perform a certain function whenever there’s a change in an input field and isSubmitting
is also a boolean value that’s set to true whenever the submit has been clicked.
The App
function also returns a div
that contains the JSX markup for the form.
On line 17, the <Form>
component is used to encompass the whole code needed for the form. As mentioned above, it helps to prevent typing out <form onSubmit={props.handleSubmit}/>
.
I’ll highlight the different Field
components and input
elements in the Form component and how they work with Formik.
Text Input
The Field
component is always to set to the input element. So all that’s left is to define the name attribute so that Formik can automatically get the value.
The line of code above the Field component is simply used for validation purposes. It checks if the input element is in focus with touched.fullname
and then checks if there are any errors with errors.fullname
, if there are errors, it then shows the custom message in the object schema validator. I’ll touch on the setup for validation later.
Select
As mentioned above, the default state of a Field component is set to input but that can easily be changed by specifying a component prop as seen above. The name attribute is set to editor and has two option elements with different values. The first line of code is also used for validation as explained above.
Radio
For the radio element, we are not able to use the Field component but instead the old fashioned way with input and a type of radio. Both radio options are set to the same name attribute but each radio option has a different value.
Checkbox
The checkbox element here is the Field component with a type of checkbox. The checked event is set to change the value of the newsletter value to either a truthy or falsy value.
Submit Button
Another thing to note is the button element. Formik automatically detects that clicking on the button element at the end of the form signifies the user’s intent to submit all of the form details.
<button disabled={isSubmitting}>Submit</button>
isSubmitting
is a submitting state. It’s either set to a truthy or falsy value. When it’s set to true, the button will be disabled while Formik makes a call to the handleSubmit handler.
The DisplayFormikState
function is a stateless function that helps to show the raw data and error values in the form via the props.
FormikApp is used to encompass the whole form in the withFormik
Higher-order Component (HoC).
The mapsPropsToValues
function helps to transform the outer props we defined in the App function earlier into form values. It returns all the values gotten from the form details. The next thing to note is the validationSchema
.
The validationSchema is what helps with the form validation. It uses Yup, which is an object schema validator to achieve that.
As you can see above, the name of the value is set to Yup, and then you decide if the value should be a string, number, boolean or date. You can also make sure that the value is required by chaining required()
and putting the error message inside the parenthesis.
Yup also allows you to set the minimum length of a string with the min()
API. It accepts two values, the number of characters and the error message if the value is not up to that amount.
You can check out the Yup documentation for more API and how you can validate your forms better.
The handleSubmit
function is the submission handler, it accepts values
(which contains the form details), resetForm
which is used to reset all the form values, setErrors
which sets the error messages if there are errors and setSubmitting
is used to set isSubmitting imperatively.
Our form is then wrapped in the withFormik
HoC and FormikApp
is rendered on the React App.
With all the code above, you can see how easy, Formik makes building React apps.
Conclusion
In this tutorial, we went through a guide how to build forms with React. I highlighted the fact that React does not ship with a default way of handling forms, instead, you’d have to use the handleChange
event to check for changes and at the same time sync with the state. I also explained the basics of Controlled Components in React.
We also used Formik, which is basically a HoC that helps with the building of forms. We went through the different APIs that ships with Formik and also how to use them.
The demo and code for the form built with Controlled Components can be seen on CodeSandbox and that of Formik can be seen here too.
Full visibility into production React apps
Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.