Back

Data Validation with React Final Form

Data Validation with React Final Form

Form validation is critical for preventing malicious attacks or incorrect data from being inputted into an application. The React Final Form zero-dependency library is a form validation tool that provides form management for validating forms in react applications. It is written in core JavaScript, which means it can be used in other JavaScript frameworks. In this article, we will look at this library and how to use it in a web application.

Getting started with React Final Form

In this section, we will create a React Application by running the following command in your project terminal:

npx create-react-app react-final-form
cd react-final-form

We will install React Final Form by using either of the code blocks below:

yarn add final-form react-final-form final-form-focus

or

npm install final-form react-final-form final-form-focus

To style our application, replace the code in App.css by pasting the entire code block from this link

Validating Forms with React Final Form

It is very simple to validate forms using the React Final Form library; we will look at record-level validation, field-level validation, and error-focused validation.

To make use of the library, use the code block below:

import './App.css';
import React from 'react'
import { Form, Field } from 'react-final-form'

const App = () =>  {
  const logValues = values => {
    window.alert(JSON.stringify(values, 0, 2))
  }
  
  return (
    <div className='App'>
    <main >
    <h1>React Final Form</h1>
  
    <Form
      onSubmit={logValues}
      initialValues={{ firstName: "John"}}
    >
      {({ handleSubmit }) => (
        <form className="form" onSubmit={handleSubmit}>
          <div>
            <label>First Name</label>
            <Field
              name="firstName"
              component="input"
              type="text"
              placeholder="First Name"
              className="Input"
            />
          </div>
          <div className="button-container">
            <button type="submit">
              Submit
            </button>
          </div>
        </form>
      )}
      
      </Form>
    </main>
  </div>
  )
}

export default App;

We imported Form and Field from react-final-form into the above code block. The only required prop for the Form component is the onSubmit prop. The initialValues and render props are optional; the render prop can be passed to the Form component as a child. It provides the handleSubmit function via the render prop, passed to the HTML form onSubmit. We will then create a Field component with the required/optional props.

Basic use of React Final Form

Record level validation

Record-level validation utilizes a validate prop on the Form component, which contains all form values and returns an object containing the structure of the values in the Form component.

...
  return (
    <div className="App">
      <main>
        <h1>React Final Form</h1>

        <Form
          onSubmit={logValues}
          validate={(values) => {
            const errors = {};
            if (!values.username) {
              errors.username = "required";
            }
            if (!values.password) {
              errors.password = "Required";
            }
            if (!values.confirm) {
              errors.confirm = "Required";
            } else if (values.confirm !== values.password) {
              errors.confirm = "Must match";
            }
            return errors;
          }}
        >
          {({ handleSubmit, submitting, form, pristine }) => (
            <form onSubmit={handleSubmit}>
              <Field name="username">
                {({ input, meta }) => (
                  <div>
                    <label>Username</label>
                    <input
                      {...input}
                      type="text"
                      className="Input"
                      placeholder="Username"
                    />
                    <span className="errorMsg">
                      {meta.error && meta.touched && <span>{meta.error}</span>}
                    </span>
                  </div>
                )}
              </Field>
              <Field name="password" type="password">
                {({ input, meta }) => (
                  <div>
                    <label>Password</label>
                    <input
                      {...input}
                      className="Input"
                      type="password"
                      placeholder="Password"
                    />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <Field name="confirm" type="password">
                {({ input, meta }) => (
                  <div>
                    <label>Confirm</label>
                    <input {...input} className="Input" placeholder="Confirm" />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <div className="button-container">
                <button type="submit" disabled={submitting}>
                  Submit
                </button>
                <button
                  type="button"
                  onClick={form.reset}
                  disabled={submitting || pristine}
                  className="reset"
                >
                  Reset
                </button>
              </div>
            </form>
          )}
        </Form>
      </main>
    </div>
  );
...

Using the validate prop on the Form component, we can check if the values passed into the Form are valid or invalid in the above code block. Other useful values in the render prop we destructured include submitting, form, and pristine, which we can use to validate the form when we click the submit or reset button.

The Field components have a fieldState that contains the input and meta properties. These are destructured from fieldState, and we added the HTML input element as a child to the Field component, spreading the input from the fieldState to the HTML input. meta is a helpful property from feildState, it has a few values that are used to keep track of the state of the field, this can be used to display helpful error messages on the form using meta.touched and meta.error.

Record level Validation

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

Field-level validation

The Field component has a validate prop to which we can directly pass validation rules; if the value is valid, it returns undefined; otherwise, it returns the error message.

Using the Field-level validate prop, we can successfully define custom validation rules.

...
const App = () => {
...

  const required = (value) => (value ? undefined : "Required");
    
  const mustBeNumber = (value) =>
    isNaN(value) ? "Must be a number" : undefined;
    
  const minValue = (min) => (value) =>
    isNaN(value) || value >= min ? undefined : `Should be greater than ${min}`;
    
  const composeValidators =
    (...validators) =>
    (value) =>
      validators.reduce(
        (error, validator) => error || validator(value),
        undefined
      );
...

  return (
    <div className="App">
      <main>
        <h1>React Final Form</h1>

        <Form
          onSubmit={logValues}
          validate={(values) => {
            const errors = {};
            if (!values.username) {
              errors.username = "required";
            }
            return errors;
          }}
        >
          {({ handleSubmit, submitting, form, pristine }) => (
            <form onSubmit={handleSubmit}>
              <Field name="firstName" validate={required}>
                {({ input, meta }) => (
                  <div>
                    <label>First Name</label>
                    <input {...input} className="Input"  type="text" placeholder="First Name" />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <Field
                name="age"
                validate={composeValidators(
                  required,
                  mustBeNumber,
                  minValue(18)
                )}
              >
                {({ input, meta }) => (
                  <div>
                    <label>Age</label>
                    <input {...input} className="Input"  type="text" placeholder="Age" />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <div className="button-container">
                <button type="submit" disabled={submitting}>
                  Submit
                </button>
                <button
                  type="button"
                  onClick={form.reset}
                  disabled={submitting || pristine}
                  className="reset"
                >
                  Reset
                </button>
              </div>
            </form>
          )}
        </Form>
      </main>
    </div>
  );
};
...

Four sets of field-level validation rules were created in the preceding code block: required, mustBeNumber, minValue, and composeValidators. To validate a component, the composeValidators can accept multiple validation rules. We can combine record and field-level validations to add validation to a form.

Field level valid

Error-focused validation

We can validate other input fields, select, checkbox, and radio buttons using record-level validation. You can also add the decorators prop to the Form component if you have a large form and want to easily help a user auto-detect where validation failed when submitting a form.

...
import createDecorator from "final-form-focus";
...
const App = () => {
...
  const focusOnError = createDecorator();
...
  return (
    <div className="App">
      <main>
        <h1>React Final Form</h1>
        <Form
          decorators={[focusOnError]}
          onSubmit={logValues}
          validate={(values) => {
            const errors = {};
            if (values.username === undefined) {
              errors.username = "Required";
            }
            if (values.position === undefined) {
              errors.position = "Required";
            }
            if (!values.terms) {
              errors.terms = "Required";
            }
            if (!values.interested) {
              errors.interested = "Required";
            }

            return errors;
          }}
        >
          {({ handleSubmit, submitting, form, pristine }) => (
            <form className="form" onSubmit={handleSubmit}>
              <Field name="username">
                {({ input, meta }) => (
                  <div className={meta.active && "active" }>
                    <label>Username</label>
                    <input
                      {...input}
                      className="Input"
                      type="text"
                      placeholder="Username"
                    />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <Field name="position" type="select">
                {({ input, meta }) => (
                  <div className={meta.active && "active" }>
                    <label>Position</label>
                    <select {...input}>
                      <option value="">-- Position --</option>
                      <option value="writer">Writer</option>
                      <option value="engineer">Engineer</option>
                      <option value="analyst">Analyst</option>
                      <option value="hr">HR</option>
                    </select>
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <Field name="terms" type="checkbox">
                {({ input, meta }) => (
                  <div className={meta.active && "active" }>
                    <label>Agree with terms</label> <input {...input} />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <h3>Want more offers?</h3>
              <Field name="interested" type="radio" value="interested">
                {({ input, meta }) => (
                  <div className={meta.active && "active" }>
                    <label>Yes</label>
                    <input {...input} />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <Field name="interested" type="radio" value="not-interested">
                {({ input, meta }) => (
                  <div className={meta.active && "active" }>
                    <label>No</label>
                    <input {...input} />
                    {meta.error && meta.touched && <span>{meta.error}</span>}
                  </div>
                )}
              </Field>
              <div className="button-container">
                <button type="submit" disabled={submitting}>
                  Submit
                </button>
                <button
                  type="button"
                  onClick={form.reset}
                  disabled={submitting || pristine}
                  className="reset"
                >
                  Reset
                </button>
              </div>
            </form>
          )}
        </Form>
      </main>
    </div>
  );
};
...

To know when a field is active, meta.active is set to true. We also used the createDecorator function from final-form-focus and assigned it to focusOnError, which we passed to the decorators prop on the Form component. This helps to auto-detect when a field is invalid upon submitting the form.

Complete form validation

Conclusion

It is incredibly simple and straightforward to manage a form’s state and validate it using React Final Form. Using the library in a React application, you may create straightforward intricate forms.

For additional details on the library, check the official documentation. To examine the complete code, check out this GitHub repository.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay