Skip to main content

Less Code, More Power - Why React-Forminate is Changing the Form Game

· 9 min read
Saeed Panahi
React-Forminate maintainer

Compare React-forminate, Formik, React-Hook-Form

Introduction

In the world of React development, form handling has long meant choosing between verbose boilerplate (Formik) or complex validation setups (React-Hook-Form). But what if you could achieve better results with 70% less code? Enter React-Forminate – a game-changer that delivers powerful validation, seamless state management, and clean syntax through a revolutionary declarative approach. Let’s examine how it outperforms traditional solutions while requiring dramatically less effort.

Let's compare three popular approaches:

  1. Formik + Yup - The established combination
  2. React-Hook-Form + Zod - The performance-focused stack
  3. React-Forminate - The new declarative solution with built-in validation

Implementation Comparison

Code Comparison: Implementing the Same Signup Form

In the following examples, we'll implement identical signup forms using three popular React form libraries:

1.React Hook Form
2.Formik
3.React-Forminate

The side-by-side comparison reveals striking differences in implementation complexity. Most notably, React-Forminate achieves the same functionality with approximately 50% fewer lines of code than its counterparts, demonstrating its superior developer efficiency while maintaining full feature parity.

1. React-Hook-Form + Zod

npm i react-hook-form @hookform/resolvers zod

Click to see: react-hook-form on NPM

React-Hook-Form Signup Form

This code example demonstrates React-Hook-Form signup form with Zod validation and Tailwind styling. Shows form registration, error handling, and password validation with schema. in React-Forminate.

Key features shown: Zod schema validation, Password strength rules, Cross-field validation, Error message handling, Tailwind CSS styling

0 lines
Typescript
1import { useForm } from "react-hook-form";
2import { zodResolver } from "@hookform/resolvers/zod";
3import { z } from "zod";
4import { ReactNode } from "react";
5
6// Define Zod schema
7const signupSchema = z
8 .object({
9 firstName: z
10 .string()
11 .max(15, "Must be 15 characters or less")
12 .nonempty("This field is required."),
13 lastName: z
14 .string()
15 .max(20, "Must be 20 characters or less")
16 .nonempty("This field is required."),
17 email: z
18 .string()
19 .email("Invalid email address")
20 .nonempty("This field is required."),
21 password: z
22 .string()
23 .nonempty("Password is required")
24 .min(8, "Password must be at least 8 characters")
25 .regex(
26 /^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]+$/,
27 "Password must contain at least one uppercase, one lowercase, one number and one special character"
28 ),
29 comparePassword: z.string().nonempty("Please confirm your password"),
30 })
31 .refine((data) => data.password === data.comparePassword, {
32 message: "Passwords must match",
33 path: ["comparePassword"],
34 });
35
36type SignupFormData = z.infer<typeof signupSchema>;
37
38const SignupForm = () => {
39 const {
40 register,
41 handleSubmit,
42 formState: { errors },
43 } = useForm<SignupFormData>({
44 resolver: zodResolver(signupSchema),
45 mode: "onBlur",
46 });
47
48 const onSubmit = (data: SignupFormData) => {
49 alert(JSON.stringify(data, null, 2));
50 };
51
52 // Helper component for required field labels
53 const RequiredLabel = ({
54 htmlFor,
55 children,
56 }: {
57 htmlFor: string;
58 children: ReactNode;
59 }) => (
60 <label
61 htmlFor={htmlFor}
62 className="block text-sm font-medium text-gray-100"
63 >
64 {children} <span className="text-red-500">*</span>
65 </label>
66 );
67
68 return (
69 <form onSubmit={handleSubmit(onSubmit)}>
70 <div className="space-y-2.5">
71 <div className="space-y-2">
72 <RequiredLabel htmlFor="firstName">First Name</RequiredLabel>
73 <input
74 id="firstName"
75 type="text"
76 {...register("firstName")}
77 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
78 />
79 {errors.firstName && (
80 <div className="text-sm text-red-600">
81 {errors.firstName.message}
82 </div>
83 )}
84 </div>
85
86 <div className="space-y-2">
87 <RequiredLabel htmlFor="lastName">Last Name</RequiredLabel>
88 <input
89 id="lastName"
90 type="text"
91 {...register("lastName")}
92 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
93 />
94 {errors.lastName && (
95 <div className="text-sm text-red-600">
96 {errors.lastName.message}
97 </div>
98 )}
99 </div>
100
101 <div className="space-y-2">
102 <RequiredLabel htmlFor="email">Email Address</RequiredLabel>
103 <input
104 id="email"
105 type="email"
106 {...register("email")}
107 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
108 />
109 {errors.email && (
110 <div className="text-sm text-red-600">{errors.email.message}</div>
111 )}
112 </div>
113
114 <div className="space-y-2">
115 <RequiredLabel htmlFor="password">Password</RequiredLabel>
116 <input
117 id="password"
118 type="password"
119 {...register("password")}
120 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
121 />
122 {errors.password && (
123 <div className="text-sm text-red-600">
124 {errors.password.message}
125 </div>
126 )}
127 </div>
128
129 <div className="space-y-2 mb-4.5">
130 <RequiredLabel htmlFor="comparePassword">
131 Confirm Password
132 </RequiredLabel>
133 <input
134 id="comparePassword"
135 type="password"
136 {...register("comparePassword")}
137 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
138 />
139 {errors.comparePassword && (
140 <div className="text-sm text-red-600">
141 {errors.comparePassword.message}
142 </div>
143 )}
144 </div>
145
146 <button
147 type="submit"
148 className="px-4 py-2 bg-[#0457aa] cursor-pointer text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
149 >
150 Submit
151 </button>
152 </div>
153 </form>
154 );
155};
156
157const ReactHookForm = () => {
158 return <SignupForm />;
159};
160
161export default ReactHookForm;
162

2. Formik + Yup

npm i formik yup

Click to see: Formik on NPM

React-Formik Signup Form

This code example demonstrates Formik signup form with Yup validation and Tailwind CSS. Shows form state management, field validation, and error handling. in React-Forminate.

Key features shown: Yup schema validation, Form state management, Password validation, Error handling, Tailwind CSS styling

0 lines
Typescript
1import { useFormik } from "formik";
2import { ReactNode } from "react";
3import * as Yup from "yup";
4
5const SignupForm = () => {
6 const formik = useFormik({
7 initialValues: {
8 firstName: "",
9 lastName: "",
10 email: "",
11 password: "",
12 comparePassword: "",
13 },
14 validationSchema: Yup.object({
15 firstName: Yup.string()
16 .max(15, "Must be 15 characters or less")
17 .required("This field is required."),
18 lastName: Yup.string()
19 .max(20, "Must be 20 characters or less")
20 .required("This field is required."),
21 email: Yup.string()
22 .email("Invalid email address")
23 .required("This field is required."),
24 password: Yup.string()
25 .required("Password is required")
26 .min(8, "Password must be at least 8 characters")
27 .matches(
28 /^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]+$/,
29 "Password must contain at least one uppercase, one lowercase, one number and one special character"
30 ),
31 comparePassword: Yup.string()
32 .required("Please confirm your password")
33 .oneOf([Yup.ref("password")], "Passwords must match"),
34 }),
35 onSubmit: (values) => {
36 alert(JSON.stringify(values, null, 2));
37 },
38 });
39
40 // Helper component for required field labels
41 const RequiredLabel = ({
42 htmlFor,
43 children,
44 }: {
45 htmlFor: string;
46 children: ReactNode;
47 }) => (
48 <label
49 htmlFor={htmlFor}
50 className="block text-sm font-medium text-gray-100"
51 >
52 {children} <span className="text-red-500">*</span>
53 </label>
54 );
55
56 return (
57 <form onSubmit={formik.handleSubmit}>
58 <div className="space-y-2.5">
59 <div className="space-y-2">
60 <RequiredLabel htmlFor="firstName">First Name</RequiredLabel>
61 <input
62 id="firstName"
63 name="firstName"
64 type="text"
65 onChange={formik.handleChange}
66 onBlur={formik.handleBlur}
67 value={formik.values.firstName}
68 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
69 />
70 {formik.touched.firstName && formik.errors.firstName ? (
71 <div className="text-sm text-red-600">
72 {formik.errors.firstName}
73 </div>
74 ) : null}
75 </div>
76
77 <div className="space-y-2">
78 <RequiredLabel htmlFor="lastName">Last Name</RequiredLabel>
79 <input
80 id="lastName"
81 name="lastName"
82 type="text"
83 onChange={formik.handleChange}
84 onBlur={formik.handleBlur}
85 value={formik.values.lastName}
86 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
87 />
88 {formik.touched.lastName && formik.errors.lastName ? (
89 <div className="text-sm text-red-600">{formik.errors.lastName}</div>
90 ) : null}
91 </div>
92
93 <div className="space-y-2">
94 <RequiredLabel htmlFor="email">Email Address</RequiredLabel>
95 <input
96 id="email"
97 name="email"
98 type="email"
99 onChange={formik.handleChange}
100 onBlur={formik.handleBlur}
101 value={formik.values.email}
102 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
103 />
104 {formik.touched.email && formik.errors.email ? (
105 <div className="text-sm text-red-600">{formik.errors.email}</div>
106 ) : null}
107 </div>
108
109 <div className="space-y-2">
110 <RequiredLabel htmlFor="password">Password</RequiredLabel>
111 <input
112 id="password"
113 name="password"
114 type="password"
115 onChange={formik.handleChange}
116 onBlur={formik.handleBlur}
117 value={formik.values.password}
118 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
119 />
120 {formik.touched.password && formik.errors.password ? (
121 <div className="text-sm text-red-600">{formik.errors.password}</div>
122 ) : null}
123 </div>
124
125 <div className="space-y-2 mb-4.5">
126 <RequiredLabel htmlFor="comparePassword">
127 Confirm Password
128 </RequiredLabel>
129 <input
130 id="comparePassword"
131 name="comparePassword"
132 type="password"
133 onChange={formik.handleChange}
134 onBlur={formik.handleBlur}
135 value={formik.values.comparePassword}
136 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
137 />
138 {formik.touched.comparePassword && formik.errors.comparePassword ? (
139 <div className="text-sm text-red-600">
140 {formik.errors.comparePassword}
141 </div>
142 ) : null}
143 </div>
144
145 <button
146 type="submit"
147 className="px-4 py-2 bg-[#0457aa] cursor-pointer text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
148 >
149 Submit
150 </button>
151 </div>
152 </form>
153 );
154};
155
156const ReactFormik = () => {
157 return <SignupForm />;
158};
159
160export default ReactFormik;
161

3. React-Forminate

npm i react-forminate

Click to see: React-Forminate on NPM

React Forminate Signup Form

This code example demonstrates React-Forminate signup form example with validation, password matching, and Tailwind CSS styling. Demonstrates clean form declaration with minimal code. in React-Forminate.

Key features shown: Declarative form setup, Built-in validation, Password matching, Tailwind CSS styling, Minimal boilerplate

0 lines
Typescript
1import { DynamicForm, type FormDataCollectionType } from "react-forminate";
2
3const SignupForm = () => {
4 const commonStyling = {
5 disableDefaultStyling: true,
6 containerClassName: "space-y-2",
7 className:
8 "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500",
9 labelClassName: "block text-sm font-medium text-gray-700 mb-2", //TailwindCSS classes
10 };
11 const formData: FormDataCollectionType = {
12 formId: "signupForm",
13 title: "Signup Form",
14 fields: [
15 {
16 fieldId: "firstName",
17 type: "text",
18 label: "First Name",
19 required: true,
20 validation: [
21 {
22 maxLength: 15,
23 message: "Must be 15 characters or less",
24 },
25 ],
26 ...commonStyling,
27 },
28 {
29 fieldId: "lastName",
30 type: "text",
31 label: "Last Name",
32 required: true,
33 validation: [
34 {
35 maxLength: 20,
36 message: "Must be 20 characters or less",
37 },
38 ],
39 ...commonStyling,
40 },
41 {
42 fieldId: "email",
43 type: "email",
44 label: "Email Address",
45 required: true,
46 validation: [{ type: "email", message: "Invalid email address" }],
47 ...commonStyling,
48 },
49 {
50 fieldId: "password",
51 type: "password",
52 label: "Password",
53 required: true,
54 validation: [{ type: "password" }],
55 ...commonStyling,
56 },
57 {
58 fieldId: "confirmPassword",
59 type: "password",
60 label: "Confirm Password",
61 required: true,
62 validation: [
63 {
64 type: "equalTo",
65 equalTo: "{{password}}", // Reference to the password field
66 message: "Passwords must match", // Custom error message
67 },
68 ],
69 ...commonStyling,
70 },
71 ],
72 };
73
74 const onSubmit = (value: any, isValid: boolean) => {
75 console.log("value", value);
76
77 if (isValid) {
78 alert(JSON.stringify(value, null, 2));
79 }
80 };
81
82 return <DynamicForm formData={formData} onSubmit={onSubmit} />;
83};
84
85const ReactForminateForm = () => {
86 return <SignupForm />;
87};
88
89export default ReactForminateForm;
90

Feature Comparison

FeatureFormik + YupRHF + ZodReact-Forminate
Lines of Code120+130+30-50
Built-in Validation
Declarative API
Performance
Schema Required
Field DependenciesComplexComplexSimple ({{field}})
Learning CurveSteepMediumGentle

Pros and Cons Analysis

Formik + Yup

✅ Pros:

  • Mature ecosystem
  • Good documentation
  • Flexible for complex forms

❌ Cons:

  • Verbose implementation
  • Performance issues with large forms
  • Requires learning two libraries
  • Manual error handling

React-Hook-Form + Zod

✅ Pros:

  • Excellent performance
  • Strong type safety
  • Active community

❌ Cons:

  • Complex setup
  • Schema definition gets verbose
  • Steeper learning curve
  • Boilerplate code

React-Forminate

(Last but not least)

✅ Pros:

  • Minimal boilerplate
  • Built-in validation
  • Declarative configuration
  • Automatic error handling
  • Field reference system
  • Consistent styling approach
  • Faster development cycle

❌ Cons:

  • Newer library (smaller community)
  • Less customization for edge cases
  • Requires adopting its paradigm

Why React-Forminate Stands Out

  1. Developer Experience
    React-Forminate reduces form implementation code by 60-70% compared to alternatives. The declarative approach means you describe what you want rather than how to implement it.

  2. Built-in Best Practices
    Validation, error handling, and form state management come pre-configured with sensible defaults.

  3. Consistent Architecture
    Unlike mixing Formik/Yup or RHF/Zod, React-Forminate provides a unified solution where all parts are designed to work together seamlessly.

  4. Rapid Prototyping
    You can build complete, production-ready forms in minutes rather than hours.

  5. Maintainability
    The configuration-centric approach makes forms easier to modify and reason about.

Conclusion

While all three solutions are capable, React-Forminate offers significant advantages in:

  • Development speed - Build forms 3-4x faster
  • Code maintainability - 70% less code to manage
  • Learning efficiency - New developers become productive quicker
  • Consistency - Standardized form patterns across your application

For projects where developer productivity and clean code matter, React-Forminate represents the next evolution of form management in React.

Clicky