DEV Community

Cover image for React libraries for building forms and surveys
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

React libraries for building forms and surveys

Written by Hussain Arif✏️

While building forms in React can be straightforward, managing them can become complex. For example, without relying on a form library, a developer has to:

  • Handle validation: For example, with every onChange or onSubmit event, we have to tell React to check if a certain field matches the given criteria
  • Handle submissions: If a user wants to sign up for our app, we have to check if their email is present in the database, and then handle the situation from there
  • Consider performance: Sometimes, rendering complex components or using complex validation logic in our forms might lead to performance degradation

To mitigate challenges like these, we can use a form library to handle most of the heavy lifting for us. In this article, we will cover a few popular form libraries in the React ecosystem:

  • SurveyJS: A form-building library that allows developers to render JSON forms and surveys. Other than React, it includes integrations for Angular, Vue, jQuery, and Knockout
  • React Hook Form: A headless library that allows developers to handle forms without writing too much boilerplate code. This library also supports React Native
  • rc-field-form: Just like React Hook Form, rc-field-form lets users manage forms on both the web and mobile platforms. Moreover, it focuses on performance and efficiency. This is especially useful for developers who want to build lightweight apps
  • Tanstack Form: A lightweight form management library built by the TanStack team. Furthermore, it supports numerous other libraries like Vue, Angular, Lit, and more

Now that we have briefly introduced some React form libraries, let’s explore them!

Setting up our React project

Before writing code, we first have to scaffold our React project. To do so, run the following command in the terminal:

#initialize React project with Typescript:
npm create vite@latest form-libraries --template react-ts
cd form-libraries
npm install #install the needed packages to run the project.
Enter fullscreen mode Exit fullscreen mode

React form libraries: SurveyJS

As stated earlier, SurveyJS is a library geared towards building multi-page forms and surveys. It supports a variety of features, including:

  • Localization: SurveyJS supports the automatic translation of strings, thus eliminating the need for manual input
  • Visual form building: Enables users without coding expertise to create forms through a visual interface
  • Customizable pre-styled components: Offers a range of pre-designed components that can be easily customized to match specific design needs

To include SurveyJS in our project, install it via npm:

npm install survey-react-ui --save
Enter fullscreen mode Exit fullscreen mode

This block of code showcases the SurveyJS library in action:

import "survey-core/defaultV2.min.css";
import { Model, SurveyModel } from "survey-core";
import { Survey } from "survey-react-ui";
import { useCallback } from "react";

export function SurveyExample() {
  //create our schema for our form:
  const surveyJson = {
    elements: [
      //configure our fields
      {
        name: "FirstName",
        title: "Enter your first name:",
        type: "text",
      },
      {
        name: "LastName",
        title: "Enter your last name:",
        type: "text",
      },
    ],
  };
  //feed the schema into the model
  const survey = new Model(surveyJson);
  //handler function that runs when the user submits the form
  const surveyComplete = useCallback((survey: SurveyModel) => {
    const userOutput = survey.data;
    console.log(userOutput);
  }, []);
  //attach this handler to the form
  survey.onComplete.add(surveyComplete);
  return (
    <div>
      {/*Finally, pass our model and render the form*/}
      <Survey model={survey} />
    </div>
  );
}
//don't forget to render this component in App.tsx!
Enter fullscreen mode Exit fullscreen mode

In the code snippet above:

  • First, we declared the surveyJson variable, which will be the schema for our form. The schema will pass the data needed to render our form
  • Then, we initialized the surveyComplete handler. This function will run whenever the user submits the form. In this case, we are just logging out the user’s input to the console
  • Finally, we passed our model to the Survey component to render our form to the DOM

Let’s test it out! To run the project, type this command:

npm run dev
Enter fullscreen mode Exit fullscreen mode

SurveryJS Demo

React Hook Form

React Hook Form is a dependency-free library that enables efficient form validation. It boasts the following features:

  • Subscriptions: React Hook Form lets developers watch individual inputs without re-rendering parent components
  • Re-render isolation: The library uses performance tricks to prevent unnecessary re-rendering in their app
  • Headless: This means that developers have the freedom to choose their favorite component libraries to use with React Hook Form
  • Hook-based API: Allows components to access and manipulate local state with relative ease. This also indicates that the library follows React’s best practices

As always, the first step is to install React Hook Form in your project:

npm install react-hook-form
Enter fullscreen mode Exit fullscreen mode

Here is a piece of code that demonstrates validation in React Hook Form:

import { useState } from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
  twice: boolean;
  newjeans: boolean;
  name: string;
};
export default function ReactHookForm() {
  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm<Inputs>({ defaultValues: { twice: true, name: "LogRocket" } });
  const [output, setOutput] = useState<Inputs>();
  const onSubmit: SubmitHandler<Inputs> = (data) => setOutput(data);
  return (
    <div>
      <h1>Your favorite group?</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        {/* register your input into the hook by invoking the "register" function */}
        <label>
          <input type="checkbox" {...register("twice")} />
          Twice
        </label>
        <label>
          {" "}
          <input type="checkbox" {...register("newjeans")} />
          NewJeans
        </label>
        <label>
          {" "}
          Name
          <input type="text" {...register("name")} />
        </label>
        {/* include validation with required or other standard HTML validation rules */}
        {/* errors will return when field validation fails  */}
        <input type="submit" />
      </form>
      <p>{output?.newjeans && "newjeans was selected"}</p>
      <p> {output?.twice && "twice was selected"}</p>
      <p> Name: {output?.name} </p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Let’s break down this code piece by piece:

  • First, we created an Inputs type, which contains the fields that we want to populate
  • Then, we used the useForm Hook and passed in our fields and default values via the defaultValues property
  • Afterwards, we declared our onSubmit handler, which will update a Hook called output. We will render the output Hook to the DOM later in this code
  • Next, we used the register function on our input components. This tells React that we want to connect our inputs with the useForm Hook
  • Finally, React will display the value of the output Hook depending on the user’s input

That’s it! Let’s test React Hook Form out: React Hook Form Demo

rc-field-form

Similar to React Hook Form, rc-field-form is another React form library that focuses on performance and ease of use.

To get started, simply install the dependencies like so:

npm install rc-field-form
Enter fullscreen mode Exit fullscreen mode

This snippet shows how you can implement asynchronous validation on a text field via the rc-field-form library:

import Form, { Field } from "rc-field-form";

const Input = ({ value = "", ...props }) => <input value={value} {...props} />;
export const RCFieldFormExample = () => {
  return (
    <Form
      onFinish={(values) => {
        console.log("Finish:", values);
      }}
    >
      <Field
        name="username"
        validateTrigger={["onSubmit"]}
        rules={[
          { required: true },
          {
            validator(rule, value) {
              if (value === "Log") {
                return Promise.reject(alert("Not allowed!"));
              }
              return Promise.resolve();
            },
          },
        ]}
      >
        <Input placeholder="Username" />
      </Field>
      <Field name="password">
        <Input placeholder="Password" />
      </Field>
      <button>Submit</button>
    </Form>
  );
};
Enter fullscreen mode Exit fullscreen mode

Here’s a brief explanation of our program:

  • We rendered the Form component and defined the onFinish handler. This will tell React that it should output the user’s input to the console after submission
  • Then, we rendered two Field components: one for username and the other for password
  • Then, for validation, we configured the rules prop in the username field. Here, we instructed the library that if the username field is set to Log, it has to display an error message

Here’s the output of the code:

RC-Field-Form Demo

Nice! As you can see, our code works!

Tanstack forms with Zod

The Tanstack team needs no introduction. Recently, they introduced TanStack Form to their arsenal of high-quality utilities. Though still in beta, the library consists of an exhaustive list of features, including:

  • Headless: Just like React Hook Form, TanStack Form handles all the logic and developers can use their component library of choice
  • Asynchronous validation
  • Lightweight
  • Hook-based design

As always, the first step to use a library is to install it:

#react-form: to render and build forms
#zod-form-adapter and zod: For validation purposes.
npm i @tanstack/react-form @tanstack/zod-form-adapter zod
Enter fullscreen mode Exit fullscreen mode

Next, let’s build a component that informs the user in case a validation error occurs:

import type { FieldApi } from "@tanstack/react-form";

function FieldInfo({ field }: { field: FieldApi<any, any, any, any> }) {
  return (
    <>
      {/*If error occurs, display it. */}
      {field.state.meta.isTouched && field.state.meta.errors.length ? (
        <em>{field.state.meta.errors.join(",")}</em>
      ) : null}
      {field.state.meta.isValidating ? "Validating..." : null}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, we created a component called FieldInfo, which will check if the user has failed any validation checks. If this condition is met, it will display an error on the page.

Finally, write this block of code for form rendering and validation using Tanstack Form:

import { useForm } from "@tanstack/react-form";
import { zodValidator } from "@tanstack/zod-form-adapter";
import { z } from "zod";

//step 1: define our schema and each field's requirements
const userSchema = z.object({
  //if the user's name is Log, display an error.
  firstName: z.string().refine((val) => val !== "Log", {
    message: "[Form] First name cannot be Log!",
  }),
  //the user's age should be atleast 18.
  age: z.coerce.number().min(18, "Need to be an adult!"),
});

type User = z.infer<typeof userSchema>;
export default function TanstackFormExample() {
  //use the useForm hook and define our default values.
  const form = useForm({
    defaultValues: {
      firstName: "",
      age: 0,
    } as User,
    onSubmit: async ({ value }) => {
      console.log(value);
    },
    //configure our validation
    validatorAdapter: zodValidator(),
    validators: {
      onSubmit: userSchema,
    },
  });
  return (
    <div>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          e.stopPropagation();
          form.handleSubmit();
        }}
      >
        <div>
          {/*Create our fields and inputs*/}
          <form.Field
            name="firstName"
            children={(field) => {
              return (
                <>
                  <label htmlFor={field.name}>First Name:</label>
                  <input
                    id={field.name}
                    name={field.name}
                    value={field.state.value}
                    onBlur={field.handleBlur}
                    onChange={(e) => field.handleChange(e.target.value)}
                  />
                  <FieldInfo field={field} />
                </>
              );
            }}
          />
        </div>
        <div>
          {/*Second input for the user's age*/}
          <form.Field
            name="age"
            children={(field) => (
              <>
                <label htmlFor={field.name}>Age:</label>
                <input
                  id={field.name}
                  name={field.name}
                  value={field.state.value}
                  onBlur={field.handleBlur}
                  onChange={(e) => field.handleChange(e.target.valueAsNumber)}
                />
                <FieldInfo field={field} />
              </>
            )}
          />
        </div>
        <div></div>
        {/*This component will run when the form's state changes*/}
        <form.Subscribe
          selector={(state) => {
            return state;
          }}
          children={(state) => {
            return (
              //check if the user can submit the form:
              <button type="submit" disabled={!state.canSubmit}>
                {state.isSubmitting ? "..." : "Submit"}
              </button>
            );
          }}
        />
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This code might seem daunting at first, but most of it is boilerplate needed for the library to run. The explanation is in the code comments.

When run, the TanStack Form output should look like so:

TanStack Form Demo<br>

Comparing the React form libraries we covered

Here is a table that compares the libraries we covered in this guide:

Library Documentation Community Updates Performance-focused Notes
SurveyJS Easy to follow and concise Large and growing Updates frequently No [Paid tier](https://surveyjs.io/pricing) for large projects, [part of a form library ecosystem](https://surveyjs.io/try/reactjs)
React Hook Form Easy to follow and concise Extremely large and rapidly growing Updates frequently Yes
rc-field-form A bit hard to follow. Not as easy as other libraries Small but growing Updates frequently Yes Built by the [Ant Design team](https://ant.design/), well-funded and unlikely to be abandoned.
Tanstack Form Easy to follow Large and growing Updates extremely frequently Yes Currently in beta, API breakages possible

In this article, we learned about a few form-building libraries in React. Additionally, we also covered how we can perform rendering and validation on our forms in these libraries. In my hobby projects, I use React Hook Form because it offers a reliable and performant solution with a user-friendly developer experience. Here is the source code of the article.

Of course, depending on a project’s needs, users can also opt for libraries like Formik or Ant Design for form management.

Thank you for reading!


Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
Enter fullscreen mode Exit fullscreen mode

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Enter fullscreen mode Exit fullscreen mode
  1. (Optional) Install plugins for deeper integrations with your stack:
  2. Redux middleware
  3. ngrx middleware
  4. Vuex plugin

Get started now

Top comments (0)