So, you’ve started building React apps and now you’re wondering how to 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 get input from different form components and submit the data in a React application.
Finally, I’ll show you how to use the Formik library to build all kinds of forms, from simple to complex.
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"/>
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
.
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, then set to that state value in the render()
function.
import React, { useState } from 'react' ​​ ​​export default function App() { ​​ const [inputValue] = useState('') ​​ ​​ return ( ​​ <div className="App"> ​​ <form> ​​ <label>Name</label> ​​ <input type="text" value={inputValue} /> ​​ <input type="submit" value="Submit" /> ​​ </form> ​​ </div> ​​ ) ​​}
As you can see in the code block above, the input
element has a value of inputValue
, which means the value of the input element is set to be in sync with the inputValue
in the state. So whatever is typed in the input field will be stored in the state, but there’s a problem here.
​​React does not automatically detect changes in the input element. For that, we use an onChange
​ form event to check for changes.
​​import React, { useState } from 'react' ​​ ​​export default function App() { ​​ const [inputValue, setInputValue] = useState('') ​​ ​​ const handleChange = (e) => { ​​ setInputValue(e.currentTarget.value) ​​ } ​​ ​​ return ( ​​ <div className="App"> ​​ <form> ​​ <label>Name</label> ​​ <input type="text" value={inputValue} onChange={handleChange} /> ​​ <input type="submit" value="Submit" /> ​​ </form> ​​ </div> ​​ ) ​​}
Here, the input element now has an additional onChange
event. The onChange
event will be executed whenever there’s a change in the 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. React knows how to store the values from the input element to the state now, but how do we deal with the form submission? Take a look at the code block below:
import React, { useState } from 'react' ​​ ​​export default function App() { ​​ const [inputValue, setInputValue] = useState('') ​​ ​​ const handleChange = (e) => { ​​ setInputValue(e.currentTarget.value) ​​ } ​​ ​​ const handleSubmit = (e) => { ​​ console.log(`Form value: ${inputValue}`) ​​ e.preventDefault() ​​ } ​​ ​​ return ( ​​ <div className="App"> ​​ <form onSubmit={handleSubmit}> ​​ <label>Name</label> ​​ <input type="text" value={inputValue} onChange={handleChange} /> ​​ <input type="submit" value="Submit" /> ​​ </form> ​​ </div> ​​ ) ​​}
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 whole process 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
, etc.
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.
See the Pen
LogRocket: An imperative guide to forms in React by Simohamed (@smhmd)
on CodePen.
Input
in React forms<div className="field"> ​​ <label className="label">Full Name</label> ​​ <div className="control"> ​​ <input ​​ className="input" ​​ type="text" ​​ name="fullName" ​​ value={fullName} ​​ onChange={handleChange} ​​ /> ​​ </div> ​​</div>
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 as the correspondent property on our state object.
This is also why there’s a change in the handleChange
function.
const handleChange = (event) => { ​​ const target = event.target ​​ const value = target.type === 'checkbox' ? target.checked : target.value ​​ const name = target.name ​​ ​​ setState((state) => ({ ​​ ...state, ​​ [name]: value ​​ })) ​​}
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
​​<div className="field"> ​​ <label className="label">What do you like about React</label> ​​ <div className="control"> ​​ <textarea ​​ className="textarea" ​​ name="message" ​​ value={state.message} ​​ onChange={handleChange} ​​ /> ​​ </div> ​​</div>
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
​​<div className="field"> ​​ <label className="label">Pick your editor</label> ​​ <div className="control"> ​​ <div className="select"> ​​ <select ​​ value={state.editor} ​​ name="editor" ​​ onChange={handleChange} ​​ > ​​ <option value="vscode">VSCode</option> ​​ <option value="vim">Vim</option> ​​ </select> ​​ </div> ​​ </div> ​​</div>
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, which is why each of the option tags has its own value
attribute with unique contents.
Checkbox
<div className="field"> ​​ <div className="control"> ​​ <label className="checkbox"> ​​ <input ​​ name="terms" ​​ type="checkbox" ​​ checked={state.terms} ​​ onChange={handleChange} ​​ /> ​​ I agree to the{' '} ​​ <a href="https://google.com">terms and conditions</a> ​​ </label> ​​ </div> ​​</div>
The implementation of the checkbox
element in React forms is a bit different from the others above. Instead of setting the 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, which means either a truthy or falsy value.
​​const [state, setState] = React.useState({ ​​ fullName: '', ​​ emailAddress: '', ​​ password: '', ​​ editor: '', ​​ message: '', ​​ terms: false, ​​ test: '' ​​})
Radio
in React​​<div className="field"> ​​ <div className="control"> ​​ <label className="label">Do you test your React code?</label> ​​ <br /> ​​ <label className="radio"> ​​ <input ​​ type="radio" ​​ name="test" ​​ onChange={handleChange} ​​ value="Yes" ​​ checked={state.test === 'Yes'} ​​ /> ​​ Yes ​​ </label> ​​ <label className="radio"> ​​ <input ​​ type="radio" ​​ name="test" ​​ onChange={handleChange} ​​ value="No" ​​ checked={state.test === 'No'} ​​ /> ​​ No ​​ </label> ​​ </div> ​​</div>
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
whenever either of the two is selected.
If you think setting up React forms in the manner above is a bit stressful and worrisome, then I have good news for you. Formik 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 use 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​
, andhandleSubmit
​) to your form via props​.handleChange
​ andhandleBlur
​ work exactly as expected — they use a name​ or id​ attribute to figure out which field to update.
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 when building React forms with Formik.
When it comes to the Formik API, there are three important APIs to consider and understand:
withFormik
​ higher-order component​​Field
​ component​​Form​
componentwithFormik
higher-order componentwithFormik
allows you to create higher-order React components. 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​
: as the name suggests, it helps with the form submission in Formik. It is automatically passed the form values and any other props wrapped in the componentmapPropsToValues
: is used to initialize the values of the form state. Formik transfers the results of mapPropsToValues​ into an updatable form state and makes these values available to the new component as props.values
.validationSchema​
: a function that returns a Yup Schema or an actual Yup schema itself. This helps with validation inside the formNow, let’s check out some examples to make use of even more components.
Field
component in FormikThe 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 is always set to the input
element. That can easily be changed by specifying a component prop.
// default input element set to an email type <Field type="email" name="email" placeholder="Email" /> // In this case, the Field acts as a select element <Field component="select" name="color" > <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </Field>
<Form/>
component<Form/>
is a helper component ​​that extends the native form​ element. ​​It automatically assigns the onSubmit
​ event handler to props.handleSubmit​
. ​​All we need to do is wrap our Field
​ components inside a Form
​ element, like the code below:
<Form> <Field type="email" name="email" placeholder="Email" /> <Field component="select" name="color" > <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </Field> </Form>
With those basics understood, let’s see how Formik can be used to build and validate a React form.
Like controlled components, a full-fledged form will be built. We’ll then go over the different parts of the form and how it works. The entire code/demo can be viewed below.
Notice the first lines of code are imports. We import React
, render
from react-dom
, some components from Formik
with named imports. Yup is also imported.
import React from 'react'; import { render } from 'react-dom'; import { withFormik, Form, Field } from 'formik' import Yup from 'yup'
The next block of code is ​​our App
​ component definition with a props
​ param. The props
param is set to an object that contains values that will be used in the app.
const { values, errors, touched, handleChange, isSubmitting } = props;
All of the details 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.
{touched.fullname && errors.fullname && <p>{errors.fullname}</p>} <Field className="input" type="text" name="fullname" placeholder="Full Name" />
The Field
component is always set to the input element. All that’s left is to define the name attribute so that Formik can automatically get the value.
In 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
{touched.editor && errors.editor && <p>{errors.editor}</p>} <div className="control"> <Field component="select" name="editor"> <option value="atom">Atom</option> <option value="sublime">Sublime Text</option> </Field> </div>
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
{touched.test && errors.test && <p>{errors.test}</p>} <div className="control"> <label class="radio"> <input name="test" type="radio" value="yes" className="radio" onChange={handleChange} /> Yes </label> <label className="radio"> <input name="test" type="radio" value="no" className="radio" onChange={handleChange} /> No </label> </div>
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
<label className="checkbox"> {touched.newsletter && errors.newsletter && <p>{errors.newsletter}</p>} <Field type="checkbox" name="newsletter" checked={values.newsletter} /> Join our newsletter? </label>
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
buttonAnother 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 true
or false
. 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.
const DisplayFormikState = props => <div style={{ margin: '1rem 0', background: '#f6f8fa', padding: '.5rem' }}> <strong>Injected Formik props (the form's state)</strong> <div> <code>errors:</code> {JSON.stringify(props.errors, null, 2)} </div> <div> <code>values:</code> {JSON.stringify(props.values, null, 2)} </div> <div> <code>isSubmitting:</code> {JSON.stringify(props.isSubmitting, null, 2)} </div> </div>;
FormikApp is used to encompass the whole form in the withFormik
higher-order component.
const FormikApp = withFormik({ mapPropsToValues({ email, password, newsletter, editor, test }) { return { email: email || '', password: password || '', newsletter: newsletter || false, editor: editor || 'atom', test: test || '' } }, validationSchema: Yup.object().shape({ email: Yup.string().email('Email not valid').required('Email is required'), fullname: Yup.string().required('Full Name is required!'), password: Yup.string().min(9, 'Password must be 9 characters or longer').required('Password is required') }), handleSubmit(values, { resetForm, setErrors, setSubmitting }) { setTimeout(() => { if (values.email === '[email protected]') { setErrors({ email: 'That email is already taken' }) } else { resetForm() } setSubmitting(false) }, 2000) } })(App)
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 a date. You can also ensure that the value is required by chaining required()
and putting the error message inside the parentheses.
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 to 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.
In this tutorial, we learned how to build forms with React. 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 an HoC that helps with the building of forms. We went through the different APIs that ship with Formik and 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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "A guide to React forms and events using Formik"
fantastic blog thanks for socializing