TL;DR? I made a library to compete with Formik and React Hook Form called "HouseForm". It would mean a lot if you looked at it, gave feedback on it, and maybe gave it a star on GitHub.
If you've looked into form validation with React, you'll likely have heard of Formik. My first run-in with Formik was at a large company I worked; it was already established as the go-to form library for our projects, and I immediately fell in love with it.
My time at this company was in 2019, right before Formik surpassed one million weekly downloads. Thanks to my experience using Formik at this company, I was left with a strong recommendation in favor of the tool for all future React forms usage.
Fast forward to today. I'm leading a front-end team in charge of many React and React Native applications. One such application we inherited was very heavily form-focused. Formik is still the wildly popular, broadly adopted intuitive forms API I used all those years ago.
So, if we loved Formik, why did we not only remove it from our projects but replace it with a form library of our own?
I think this question is answered by taking a look at the whole story:
- Why is Formik great?
- Why don't we want to use Formik?
- What can be improved about Formik?
- What alternatives are there?
- Why did we make our own form library?
- How does our own form library differ?
- How did we write it?
- What's next?
Why is Formik great?
Let's take a step back from Formik for a second. I started web development in 2016 with the advent of Angular 2. While it has its ups and downs, one of its strengths is in its built-in abilities to do form validation - made only stronger when recent versions of Angular (namely, 14) introduced fully typed forms.
React doesn't have this capability baked in, so during my early explorations into the framework I was dearly missing the ability to do validated forms for more complex operations.
While an Angular form might look something like this:
@Component({
selector: 'my-app',
standalone: true,
imports: [CommonModule, FormsModule, ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()" >
<label>
<div>Name</div>
<input type="text" required minlength="4" formControlName="name">
</label>
<button>Submit</button>
<div *ngIf="form.controls.name.invalid && (form.controls.name.dirty || form.controls.name.touched)">
<div *ngIf="form.controls.name.errors?.['required']">
Name is required.
</div>
<div *ngIf="form.controls.name.errors?.['minlength']">
Name must be at least 4 characters long.
</div>
</div>
</form>
`,
})
export class App {
form = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(4)]),
});
onSubmit() {
if (!this.form.valid) return;
alert(JSON.stringify(this.form.value));
}
}
The React version (without any libraries) might look something like this:
function runValidationRulesGetErrors(rules, val) {
return rules.map((fn) => fn(val)).filter(Boolean);
}
export default function App() {
const [form, setForm] = useState({
name: { value: '', isTouched: false, isDirty: false },
});
const validationRules = {
name: [
(val) => (!!val ? null : 'Name is required.'),
(val) =>
!!val && val.length >= 4
? null
: 'Name must be at least 4 characters long.',
],
};
const [errors, setErrors] = useState({ name: [] });
const runValidation = (name, val) => {
const errors = runValidationRulesGetErrors(validationRules[name], val);
setErrors((v) => {
return {
...v,
[name]: errors,
};
});
};
const onFieldChange = (name, val) => {
setForm((v) => {
return {
...v,
[name]: {
...v[name],
isDirty: true,
value: val,
},
};
});
runValidation(name, val);
};
const onFieldBlur = (name) => {
setForm((v) => {
return {
...v,
[name]: {
...v[name],
isTouched: true,
},
};
});
runValidation(name, form[name].value);
};
const onSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify(form));
};
return (
<form onSubmit={onSubmit}>
<label>
<div>Name</div>
<input
value={form.name.value}
onChange={(e) => onFieldChange('name', e.target.value)}
onBlur={() => onFieldBlur('name')}
type="text"
/>
</label>
<button>Submit</button>
{errors.name.length !== 0 && (form.name.isDirty || form.name.isTouched) && (
<div>
{errors.name.map((error) => (
<div key={error}>{error}</div>
))}
</div>
)}
</form>
);
}
That's a difference of ~50 LOC for the Angular version vs. 90 LOC for the React version.
Clearly something needed changing in the React ecosystem.
How Formik saved the day
Here's the previous React code sample, but this time using Formik:
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const schema = Yup.object().shape({
name: Yup.string()
.min(4, 'Name must be at least 4 characters long.')
.required('Name is required.'),
});
export default function App() {
return (
<Formik
initialValues={{
name: '',
}}
validationSchema={schema}
onSubmit={(values) => {
alert(JSON.stringify(values));
}}
>
{({ errors, touched, dirty }) => (
<Form>
<label>
<div>Name</div>
<Field name="name" />
</label>
<button>Submit</button>
{errors.name && (touched.name || dirty) && <div>{errors.name}</div>}
</Form>
)}
</Formik>
);
}
'Nough said?
Not only is this example shorter than even the Angular example, but it's significantly easier to follow the flow of what's happening and when. On top of this, we're able to use existing validation logic from the exceedingly popular Yup library to make sure our form follows a consistent schema.
Is it any wonder I fell in love with Formik the first time I used it?
Why don't we want to use Formik?
We've talked a lot about my past with Formik in this article; Fast forward to today. Nowadays, I'm leading a small frontend team in charge of a plethora of applications. One such application we inherited is very heavily form-focused:
This is not a real screenshot from the app, but is a mockup used to reflect how heavily form-heavy it is. We have multiple pages like this in our app; all of which with more fields than are displayed here.
While this kind of application may seem simple at first glance, there's a lot of moving parts to it. Ignoring the other functionality within the app, this type of form page might contain:
- On blur field formatting
- Per-field validation type (Some fields validate on field blur, some validate on value change)
- Detection of if a field is touched or dirty
- Internationalized error messages
As a result, our hand-written field validation code was quickly getting out-of-hand. Because of the difficulty in maintaining that much complexity by hand, bugs, regressions, and otherwise unexpected behavior started occurring. What's worse; One form page would differ wildly in implementation from another, leading to inconsistent user experience.
While this was okay for a short while; while we were under crunch time and this project was not a high priority - it quickly became a thorn in the side.
As such, I asked one of the engineers on my team to look into React form libraries; pointing them towards Formik as a reference of what I knew existed in the ecosystem.
After a day or two of research that engineer came back: They liked Formik but had some concerns over its maintenance.
See, when they went to the Formik GitHub repository, they noticed the high number of issues and pull requests.
When they then realized that its last release date was in 2021 - nearly 2 years ago - they wanted to look into it more:
After looking into it more, there were no fewer than three separate issues asking if the project was still under maintenance.
"No problem," we thought, "surely there must be a community fork of the project."
After some time looking into it, we found a single option: An individual contributor by the name of johnrom
hacking away at a version 3.
It's sincerely impressive! While the main v3 PR we linked has 97 commits, John also started working on documentation for this potential v3 release with an additional 76 commits.
Unfortunately, he's made it clear that he's not a maintainer of Formik and admits:
[...] whether my changes are ever merged into Formik itself isn't up to me [...]
It was clear to use that it was time to find an alternative to Formik.
I want to be very explicit here; neither Jared nor John owe us anything. Their contributions to the ecosystem are not assured, nor should they be.
Almost all open-source maintainers are unpaid for their work, and it's an immense responsibility to bear the load. You constantly have to keep up with the outside ecosystem changes, manage others' contributions, answer questions, and more. It's exceedingly easy to burn out from a project with such immense loads and little personal return.
I'm very grateful for their work on Formik and admire their engineering capabilities and even their abilities to maintain and upkeep Formik while they did. Their work on Formik should be celebrated, not chastised - even while dormant.
What alternatives are there?
After looking through GitHub issues, forums, and chats, there appears to be two primary alternatives to Formik available today:
While React Final Form initially looked promising - it's only seen 5 commits to the main
branch since 2021, and has over 300 issues.
Let's check on the React Hook Form GitHub and see if things are more lively:
WOW! Now that's an actively maintained repository!
Looking further into React Hook Form, we found ourselves enjoying the basic examples:
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
console.log(watch("example")); // watch input value by passing the name of it
return (
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input defaultValue="test" {...register("example")} />
{/* include validation with required or other standard HTML validation rules */}
<input {...register("exampleRequired", { required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
In particular, we really liked the ability to do per-field validation right inline with the input itself:
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true, maxLength: 20 })} />
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
<input type="number" {...register("age", { min: 18, max: 99 })} />
<input type="submit" />
</form>
);
}
This allows us to keep our UI and our validation logic collocated in the same part of the code without having to cross-reference multiple locations of code to see how a field looks and acts.
Unfortunately, as we read deeper into this functionality, we found that it doesn't support Yup
or Zod
validation. To use either of these tools to validate your fields, you must use a schema object validator to validate the whole form:
import { useForm } from "react-hook-form";
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from "yup";
const schema = yup.object({
firstName: yup.string().required(),
age: yup.number().positive().integer().required(),
}).required();
export default function App() {
const { register, handleSubmit, formState:{ errors } } = useForm({
resolver: yupResolver(schema)
});
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<p>{errors.firstName?.message}</p>
<input {...register("age")} />
<p>{errors.age?.message}</p>
<input type="submit" />
</form>
);
}
What's more; we noticed that most of the form examples used HTML and a register
function. We were curious how this worked, so we did a bit of a deeper dive into their docs page and found that React Hook Form is, by default, uncontrolled and leaves the state persistence up to the DOM.
While this works okay for web applications, React Native doesn't really support this functionality.
To sidestep this problem, RHF introduces a Controller
API that allows you to treat your Field
s as render functions:
import { useForm, Controller } from "react-hook-form";
import { TextField, Checkbox } from "@material-ui/core";
function App() {
const { handleSubmit, control, reset } = useForm({
defaultValues: {
checkbox: false,
}
});
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="checkbox"
control={control}
rules={{ required: true }}
render={({ field }) => <Checkbox {...field} />}
/>
<input type="submit" />
</form>
);
}
This works out well and enables you to even use custom field components, but introduces a new set of headaches; There's now multiple ways of building out a field in React Hook Form.
You have to establish social rules within your team about which ways to do things, and potentially introduce abstractions to enforce these rules.
Surely, we can make some improvements overall.
What can be improved about Formik?
Let's take a more focused look at what a large form component with 15 input fields might look like when using Formik. We'll take a look at what one field being rendered might look like:
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const schema = Yup.object().shape({
firstName: Yup.string()
.required('First name is required.'),
middleInitials: Yup.string(),
lastName: Yup.string()
.required('First name is required.'),
email: Yup.string().email('Invalid email address').required('Email is required.'),
// Imagine there are 15 more fields here
});
export default function App() {
return (
<Formik
initialValues={{
name: '',
}}
validationSchema={schema}
onSubmit={(values) => {
alert(JSON.stringify(values));
}}
>
{({ errors, touched, dirty }) => (
<Form>
{/* Imagine there are 15 more fields here */}
<Field name="firstName">
{({
field,
meta,
}) => (
<div>
<label>
<div>First Name</div>
<input type="text" placeholder="Email" {...field} />
</label>
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</div>
)}
</Field>
</Form>
)}
</Formik>
);
}
Here's the challenge though; your schema
variable is defined at the top of the component file while your Field
's input
is towards the bottom of the file; meaning that you have as many lines of code in your component separating out your field's validation logic from the UI rendering behavior. That's two places to look for one concern.
And, if that's ~15 lines of code per field, that's at least 225 lines of code to sift through between the validation logic and the UI rendering, ignoring any additional logic, styling, or layout code.
What's more, Formik appears to have many ways to define a Field and Form. There's:
-
The core
Formik
component - The
Form
helper wrapper around HTML'sform
element -
The
Field
component <FastField>
useField
<ErrorMessage>
Some of these handle rendering the UI for you, some don't - meaning some work with React Native while others don't - and some of these components have multiple ways of doing the same thing.
This ability to do the same thing in multiple ways is a problem because it:
- Introduces required training of what method to use and when internally.
- Bloats the bundle size by requiring more code to be shipped.
- Increases maintenance costs of the library.
How can React Hook Form be improved?
As we mentioned in the React Hook Form section, we really liked the ability to do per-field validation using the Controller
's rules
field. However, the lack of ability to use custom validation logic - either through Zod/Yup usage or with custom functions - make this a non-starter for our use-cases.
Because of this, we run into the same issues we did with Formik's shortcomings; they don't allow you to do custom validation on the Field
(or Controller
) components themselves.
So, we know the shortcomings of RHF and Formik... What do we do now?
Why did we make our own form library?
Alright, you've read the title; I wrote an alternative library to Formik and React Hook Form called "HouseForm".
Why?
Well, I took a look at our business' needs, saw that Formik was a better choice for us than React Hook Form, but acknowledged that the maintenance of the library was a blocker for us moving forward with it.
While I'm comfortable with open-source maintenance - I maintain NPM packages that account for 7 million downloads a month - I wanted to see what the level of effort was in creating a library akin to Formik that solved our non-maintenance complaints with the tool.
After a week's worth of work, we found ourselves with a tool that we enjoyed using and solved our production needs.
How does HouseForm differ?
If you looked closely at the previous image of the HouseForm website, you'll see our single-sentence explanation of what makes HouseForm unique:
HouseForm is a field-first, Zod-powered, headless, runtime agnostic form validation library for React.
Let's walk through what that means and what a basic usage of HouseForm looks like.
First, HouseForm is "field-first". You can see this when we demonstrate an example HouseForm form:
import { Field, Form } from "houseform";
import { z } from "zod";
export default function App() {
return (
<Form
onSubmit={(values) => {
alert("Form was submitted with: " + JSON.stringify(values));
}}
>
{({ isValid, submit }) => (
<>
<Field
name="email"
onChangeValidate={z.string().email("This must be an email")}
>
{({ value, setValue, onBlur, errors }) => {
return (
<div>
<input
value={value}
onBlur={onBlur}
onChange={(e) => setValue(e.target.value)}
placeholder={"Email"}
/>
{errors.map((error) => (
<p key={error}>{error}</p>
))}
</div>
);
}}
</Field>
<button onClick={submit}>Submit</button>
</>
)}
</Form>
);
}
Here, we're using <Field onChangeValidate={}/>
to validate the field's value when the user has changed their input. This is a stark difference from Formik's single-object validation schema, because it places the validation logic right inline with the UI rendering.
Second: HouseForm is Zod-powered. Unlike Formik, which uses Yup to do validation, or RHF which requires an external dependency to use Zod; HouseForm relies more heavily on Zod to do its validation. Zod is well-loved by many React developers and seemed like a good choice for validation needs.
Third: HouseForm is headless and runtime agnostic. This means that it runs just as well in React Native, Next.js, or even Ink as it does React for the web. No differing APIs; just use the same components for each of these.
Fourth: HouseForm is flexible. Let's take the previous code sample and add validation to the email field that should run during the form submission.
// ...
<Field
name="email"
onChangeValidate={z.string().email("This must be an email")}
onSubmitValidate={isEmailUnique}
>
{({ value, setValue, onBlur, errors }) => {
// ...
}}
</Field>
// ...
// This is simulating a check against a database
function isEmailUnique(val: string) {
return new Promise<boolean>((resolve, reject) => {
setTimeout(() => {
const isUnique = !val.startsWith("crutchcorn");
if (isUnique) {
resolve(true);
} else {
reject("That email is already taken");
}
}, 20);
});
}
Notice that, outside of the isEmailUnique
logic, the only thing we had to do to add submission validation was add a onSubmitValidate
property on the Field
component.
With HouseForm, we can even extend this per-field validation logic by adding a onBlurValidate
function:
<Field
name="email"
onChangeValidate={z.string().email("This must be an email")}
onSubmitValidate={isEmailUnique}
onBlurValidate={z.string().min(1, "Your email must have at least one character")}
>
{({ value, setValue, onBlur, errors }) => {
// ...
}}
</Field>
The rules passed to each onXValidate
property can differ from one another, even within the same field. This is a super powerful API that allows you to decide:
- What fields you want validated or not
- How you want to validate each field
- At which user interaction you want to validate
- Which rules you want to validate on a per-interaction basis
Neither RHF or Formik has this capability and flexibility today.
What's next for HouseForm?
HouseForm has a lot going for it today:
- An API with tons of flexibility and capabilities.
- Runtime agnosticism, enabling it to run flawlessly in React Native.
- Extensive and usage-based tests with 97% test coverage, like this form test.
- A docs website with plenty of examples and how-to guides.
- A community-created introduction video.
- Benchmarks against other popular form libraries.
- Good first issues to encourage new contributions.
Even with all of these goodies, HouseForm isn't perfect and it never will be; No project is ever fully finished in the programming world.
Looking forward, we plan on:
-
Improving the existing
FieldArray
helper by adding utility properties. - Increasing visibility into performance by creating more benchmarks against other popular libraries.
Moving the needle further on performance; making sure it falls in-line with other libraries in the same space.
Gathering more community feedback on how to improve. (Have ideas? Open an issue!)
But we need your help! If any of this sounds interesting to you, please help us by:
- Opening GitHub issues with feature, docs, and bug requests.
- Contributing to the project with pull requests.
- Starring the project on GitHub.
That's all for now. In the next article I write, I'll be talking about how I built HouseForm using Vite and how you can build your own React library using a similar setup (it's pretty rad).
Happy form building!
Top comments (36)
We definitely need a good library for form management. Not Formik, nor R-H-F fit the bill, in my subjective view. You touch some great points in the article but (aside from code verbosity) I'm concerned about the following. For text inputs
onChange
validation is very expensive (1 validation per character).onBlur
validation requires to switch focuses back and worth, providing a somewhat clunky UX. I typically validate text inputs ondebounce
, so I'm surprised this approach is not even mentioned in the docs or repo. Though maybe adebounce
d function can be provided toonChangeValidate
โ I need to give it a try :)I believe you should be able to add
debounce
toonChange
pretty trivially, but would have to build a POC for it.Could you add a GH Discussion or GH issue for this problem so that I can remember to investigate?
github.com/houseform/houseform
Kudos to anyone who wants to make form validation nicer.
I don't have your headless restriction, and am unsure what that entails since I've never used React Native, so take what I say with a bag of salt.
I couldn't use your Field component because I'm on react-bootstrap so I'm already using someone else's Field component, and all the css etc that goes with. That's one thing I notice a lot with form helper libraries is "who gets to use their Field component and who has to dance around that? Is it css-first or js-first?"
I also don't like the function-as-children syntax. I understand it fine but it just looks really busy. I understand this isn't really a concern of yours though.
Related to the above, I would definitely say make a helper for functions of the form (e) => setValue(e.target.value) since your users will be writing that a lot. Make a version of setValue that accepts the whole event. User should only write onChange={setValue} and similar. It would also help cut down on punctuation.
I'm interested in how HouseForm deals with multiple validation errors at once from the same onBlur or whatever. How to write onBlurValidate that checks both email valid, required, belongs to the current domain, and has a non-tomato fruit in it, and shows all four validation errors at once should it need to? Currently it looks set up so only one *validate event can have one error.
Anyway, very nice work.
First; sincerely thank you for providing this feedback. Any feedback provided in a sincere way is worth more than gold.
You mentioned that you don't have my headless restriction, but in the very next line you presented a key reason why we went forward with the headless concept; It enables you to use any CSS or UI library you want with HouseForm.
stackblitz.com/edit/houseform-v1-e...
The above link is HouseForm integrated with React Bootstrap - I'll be adding it to our docs pages shortly - it was a seamless process that took me 10 minutes and I've never used React Bootstrap before. ๐ I didn't have to fiddle with an
as
key or do some strangefor
trickery, nothing like that.As for the function-as-children syntax - I hear you. It's a mixed bag overall for sure, but I'm not convinced yet that it's better than a
render={() => {}}
API, especially when you can do something exceedingly similar with thechildren={() => {}}
explicit syntax. If you have suggestions of how you'd improve this, let's chat more in a GH issue or GH discussion.As for the
e => setValue
trick - I think making a helper function will be detrimental rather than a boon. I do a fair bit of mentorship in programming, and have often come into questions of "how do I get X to work with Y?", and official recommendations throw people off hard when they're not accurate for their specific use-case. While the helpere => setValue()
function might work well for React for web or some UI libraries, it won't be universal. This type of non-universal APIs often end up bringing in support tickets and headaches for all involved.Finally, to talk about your
multiple validation errors
- we support this right out of the box! This is why we do:Instead of:
If you do any testing and see that this isn't the case, that'd be a bug, which we'd love to learn more about with a GH issue.
Once again, thank you so much for taking the time to write this. If you have any additional thoughts (or replies to the above), let me know.
Super fast update on this: We've launched a docs page outlining how to use HouseForm with external UI libraries. Even used the React Bootstrap example :)
houseform.dev/guides/ui-libraries....
They specifically mentioned being environment agnostic, so it wouldn't make sense to have a helper that is fully aware of the event of the environment it's running in.
Really interesting article and next time I'm building a greenfield react app I'll definitely look into this!
I had a very similar jourrney to you in the Vue ecosystem where the form libraries are either unmaintained, too tightly coupled to validation libraries, or have awkward/ugly syntax. The only difference was that I didn't publish my library because I couldn't face the public maintenance commitments of yet another OS project ๐
I like zod and that's why I would use houseform and even it being agnostic means I can even use it for server side validation, however I find these validation libraries's syntax always hard to grasp, but in all, nice work though!
I never seem to get Formik to work for me, which lead me to roll my own form handling implementation for every project. I've gotten deeply uninterested in other form libraries given I know how to write one myself.
It always irks me when a library unrelated to another library ties its implementation to that library. Great work but I think it's a recipe for disaster/irrelevance.
I'm not sure I follow ๐ HouseForm has zero direct dependencies and only two peer deps: React and Zod (Zod is optional). It doesn't build on top of Formik, and doesn't even use the same API.
I'm unclear how HouseForm would fall under:
Isn't the "onChangeValidate" for "Field"s tied to zod. It's particularly expecting a zod validator
Not really. It's expecting either:
If Zod isn't installed, either will still work. We have docs on this here:
houseform.dev/guides/basic-usage.h...
At the bottom. We could (and will) do better to highlight non-Zod usage in our docs
I get that one can write their own custom validator that mimics the zod API. But that just highlights my point, your validation step is explicitly/implicitly tied to zod. Which is one problem I always run into when trying to use form libraries out there.
Hence why I roll my own which doesn't tie me to any particular validation library. I understand that it's hard to create a form library that handles validation without somehow owning the validation step. You don't have to take my opinion seriously, just highlighting my frustration with form libraries.
But looking through the codebase I saw this
in
which is more like what I was expecting for form validation not tied to any validation library. So I guess I was wrong
Right, this is what I was trying to point out ๐ You can pass a custom function with validation that returns a promise. Helpful when wanting to avoid Zod and/or do async ops
Congratulations on such a great library, i have been using it in my own prod apps and really enjoying it.
Thanks so much for the kind words and the support! It was such an awesome surprise to wake up one day to the introduction video of HouseForm on your channel! ๐
What an amazing library, I was looking for alternatives to Formik and React Hook Form, I just found this for my personal project I will be using it!
Thanks so much for the kind words! :D
Let me know if you have any feedback as you use it - I'm eager to improve the library as much as possible.
I mean, I need React support though. Our applications are written in React Native (for various buisness reasons). I'm not sure that "Replace React" is a solution here.
I really like how react-hook-form handles when to validate: initially, it's on blur, then on submit, and if you submitted with mistakes it becomes on change (saying this approximately, it may be a more complex logic). This is the best for UX, so, before a form submit, user is not bothered with error messages, and after non successful submit they are clearly messaged what to change and validation message goes away right after being corrected.
I don't know, maybe your use cases are very different from typical ones, and this way of UX doesn't work for you well, but for me it's hard to imagine why would someone want to set up three different validations on the same input for on change, on blur, and on submit.
Also it's not clear what's bad about defining a whole form schema with Zod, it even gives you properly typed result afterward in the submit handler. Setting validation on each field separately seems to be more cumbersome.
Truth be told, it's just a different philosophy. Our buisness has pretty stringent requirements of what kind of requirements to do and when.
There's nothing wrong with having less control over that flow in exchange for simple to use relatively straightforward UX - just a different set of priorities.
More cumbersome? Maybe? I think most will only use
onChange
and call it done.More flexible though? 100%.
What's more, you can still use an object schema and use:
In the
onXValidate
functions in HouseForm.This is all to say; HouseForm fits our needs well but may not fit everyone's needs. That's OK - we admire and respect the other library's efforts! ๐
Don't you think it's a problem with UX? So you just start entering your name or email, and it immediately tells you that name is not long enough or email is not valid.
Developer may call it done, user may be fine with it, but what if your client will see it and will ask to change, but you can't do it as gracefully as with react-hook-form because this library wasn't designed for it.
So I think using onChange as a default option for validation is a mistake, and if client cares about UX they will ask to change this anyway, if client doesn't care of it much than it's just a not as good as it could be.
Of course, UX is subjective and for your case maybe it's better to display error messages right after entering first letter.
I think that's unfair.
This library has the ability to do pretty custom validation schemas - it absolutely was designed to do things like RHF's validation method, but be able to customize beyond that point.
Further, take a look at the example I was thinking of when I said
onChange
:frontendscript.com/wp-content/uplo...
This is a pretty common pattern to show the requirements of a password that can be done easily with HouseForm.
HouseForm is absolutely capable of pretty polished form UX without a ton of extra lift. Remember,
isTouched
andisDirty
are absolutely tools of the trade, as is conditional validation logic and more.Just to showcase that HouseForm can absolutely follow RHF's validation strategy...
Their docs claim that validation works like this:
onSubmit
onChange
How is this functionally different than:
stackblitz.com/edit/houseform-v1-e...
?
Truth is yours, I though it would be a problem, but in your example this looks simple to do.
In case when using RHF, usually no need to customize, but when there is a need I just add the
onChange
callbacks directly to the input, callonChange
from thecontrol
of controlled input, and use method of the libsetError
to set or remove the error.You know, it's a must have comments section "why you did it when X exists", but anyway that's great that you've made it, it has a different philosophy and syntax, having an alternative is always for good!
Haven't you been through lack of performance with big forms in Formik ?
You didn't mention it and it's the biggest downside I struggle with.
Especially with a lot of codependent fields and even when restricting validation on blur instead of on change events.
Does your lib handle this better ?
You know, you're not the first person I've heard this from. The challenge is that I haven't experienced this, have numbers to the contrary (more on that soon), and - until now - never had anyone provide me specific details about specifically when performance falls on it's face.
That's the bad news. Here's the good news: Performance is a key value for HouseForm. Remember, I'm using it in production for huge forms in our most business critical apps.
We actually have a set of benchmarks that we're developing to make sure we're on-par or beating common alternatives like Formik and React Hook Form (we're adding React Final Forms soon). While we only have three benchmarks currently, it shows that Formik actually outperforms React Hook Form when using RHF's
Controller
for 1,000 form fields and that HouseForm is within spiting distance of Formik:That's not all - we're actually still working on HouseForm's performance! We believe we may have a couple of methods we can still use to optimize the performance of the codebase. Some of the other performance improvements we've made previously is to conditionally recalculate the
<Form>
'sisValid
style helper variables if and only if the user is actually using the variables.That all said, now that you've been able to specifically highlight
onBlur
and codependant fields as areas of performance concerns with large-scale Formik forms, I will write benchmarks for those functionalities ASAP. I'll send a reply when we have some solid numbers for you.Thanks for the answer. I definitely try Houseform out.
Of course! Since my last message, I've added 3 more benchmarks, still looking generally in favor for HouseForm.
That said, I think I've found a possible major pitfall in terms of performance for all of the major libraries.
I've figured out a way to solve a potential performance issue, but the API is a bit wonky and I'm unclear if this is actually a problem or not.
If you're up for it and willing to share some insights as to what your performance problems are with large Formik forms, please let me know. I'd be happy to sit down and diagnose the root causes and solve them in HouseForm (or even in your app without a HouseForm migration!)
My DMs are open: twitter.com/crutchcorn or via Discord on this server: discord.gg/FMcvc6T (@crutchcorn on Discord)
@calag4n I just released version 1.3.0 of HouseForm, which includes a quick and easy way to drastically increase the performance of a form's re-render:
houseform.dev/guides/performance-o...
I'm not joking when I say that I've seen 70x performance increase in some specific edgecases in production.
I'd love to hear if this problem helps solve the performance issues you were running into with Formik.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.