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
oronSubmit
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.
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
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!
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
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
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>
);
}
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 thedefaultValues
property - Afterwards, we declared our
onSubmit
handler, which will update a Hook calledoutput
. We will render theoutput
Hook to the DOM later in this code - Next, we used the
register
function on ourinput
components. This tells React that we want to connect our inputs with theuseForm
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:
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
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>
);
};
Here’s a brief explanation of our program:
- We rendered the
Form
component and defined theonFinish
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 forusername
and the other forpassword
- Then, for validation, we configured the
rules
prop in theusername
field. Here, we instructed the library that if theusername
field is set toLog
, it has to display an error message
Here’s the output of the code:
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
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}
</>
);
}
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>
);
}
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:
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:
- Visit https://logrocket.com/signup/ to get an app ID.
- 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');
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>
- (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
Top comments (0)