Yomi Eluwande JavaScript developer. Wannabe designer and Chief Procrastinator at Selar.co and worklogs.co.

A guide to React forms and events using Formik

11 min read 3352

A Guide to React Forms and Events Using Formik

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.

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"/>

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.

We made a custom demo for .
No really. Click here to check it out.

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.

Using 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.

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. 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​, 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.

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:

  1. withFormik​ higher-order component
  2. ​​Field​ component
  3. ​​Form​ component

withFormik higher-order component

withFormik 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 component
  • mapPropsToValues: 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 form

Now, let’s check out some examples to make use of even more components.

The Field component in Formik

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 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>

The <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.

Text Input

{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 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 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.

Conclusion

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.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard 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 and mobile 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 — .

Yomi Eluwande JavaScript developer. Wannabe designer and Chief Procrastinator at Selar.co and worklogs.co.

Leave a Reply