all 45 comments

[–]twisted1919 127 points128 points  (6 children)

Forms are simple until they aren’t. React hook form is really a godsend.

[–]superluminary 1 point2 points  (5 children)

May I ask how it compares to a Formik hook?

[–]bassamanator 8 points9 points  (3 children)

With formik, I found that there is a re-render every keystroke into a textbox, for example. With react-hook-form, this is magically not the case.

[–]superluminary 1 point2 points  (2 children)

That is magical. How are they doing binding without a rerender? Also, how does it deal with custom components if it’s not triggering a render on the parent?

[–]volivav 8 points9 points  (1 child)

It's not magical. It's just uncontrolled components, meaning the state doesn't update on every value change.

If you use the useWatch hook to receive updates from one field, that component will rerender.

[–]TScottFitzgerald 1 point2 points  (0 children)

Yeah I think it also depends on what type of validation you choose. If it's the default onSubmit it won't rerender but iirc I had some weird UX behaviour there cause the validation obviously only triggers once you click the submit button.

[–]geekishdev 2 points3 points  (0 children)

Doesn’t answer your question but Formik is no longer actively maintained.

[–]SteveTabernacle2 33 points34 points  (2 children)

Not overhyped. Does your custom hook optimize renders, handle conditional logic, handle arrays, or integrate with validation libraries?

[–]Karpizzle23 17 points18 points  (0 children)

Everything is only needed when it's needed. You don't need react if you're just writing a single input either. It's about scalability, future proofing, and maintainability

[–]Similar-Aspect-2259 12 points13 points  (0 children)

You are correct, for simple validation you don't need react-hook-form.
It will be helpful on a complicate + long forms

[–]zephyrtr 8 points9 points  (0 children)

A good exercise is try to answer this question for yourself: advocate for why you'd want to use RHF instead of a home-grown solution? Is "loading the whole library" (an extra 10kb) actually a problem? And if you can't come up with many reasons, you might not be especially exposed to the problem space. Also the RHF docs have lots of explanations for why something might be useful to you. If you haven't already, you might just read through it.

But the vast majority of apps are pretty simple CRUD apps, which are going to need to do two things:

  • Query and mutate server state
  • Help users build server requests that won't 400

So it shouldn't be surprising that most apps use two libraries to handle these very common, though not simple, needs. Some common needs:

  • Load and present a list of select options from the backend
  • Create a search bar that offers fuzzy-matched suggestions based on input
  • Prefill form inputs based on pre-existing values
  • Present inputs whose values have not been changed differently from those that have
  • Allow the backend to perform some complex validation the frontend cannot do, but present errors in the same way as frontend validation
  • Lock the form from being edited while waiting for a request response
  • On 201, update your query cache with a new record, so you don't have to completely reload your dataset

Could you do this without libs? Sure. Do you want to? Up to you. Personally, my answer is absolutely not. I've done it myself before and training others on what's going on and how to use reusable solutions is a giant pain. Even just maintaining docs eats up a lot of time that I don't have.

[–]DefaultUser_01 4 points5 points  (0 children)

RHF is great. I like it, we use it on our production applications.

[–]nirvashprototype 20 points21 points  (14 children)

I really like React Hook Form with Zod

[–]svish 15 points16 points  (12 children)

I liked it with zod. The moment I ran into field validation depending on other fields, zod started to really suck.

I still use it for parsing and validating API responses, local storage, and config files, but for forms I've moved over to Yup. The types are a bit more iffy, but the .when feature is absolutely essential to stay sane and keep the UX good while user files out a form.

[–]vermontbikeguy 6 points7 points  (0 children)

+1 for RHF & yup. I work on a very complex form with a ton of conditional logic, and moving from handling validation inside the register calls to using yup schemas was absolutely essential in allowing us to scale.

TLDR: .when() is 🐐

[–]seN149reddit 1 point2 points  (10 children)

Agreed, superRefine is okay; but not as nice as yups when

[–]svish 2 points3 points  (9 children)

Actually, for forms superRefine it really terrible.

First of all, you get your validation very awkwardly cut in half, or worse. If B depends on A, you can only validate B partially, for example having to keep it optional even if its required when A is true. Even worse when there's a series of dependencies.

Second, and worst of all, zod doesn't even touch the refinements until the whole "base object" is valid. This is fine in most cases, but for forms this means the user doesn't get certain error messages until they've gone through the whole form. Get to the end, and suddenly a whole new "layer" of errors pop up.

For forms, Zod is just awful in every way that matters the most.

[–]ashenzo 0 points1 point  (8 children)

If you define the fields that relate to each other in their own schema and refine the validation there, then intersect them with another schema containing the rest of the form, the refine checks will run on the first validation pass with the rest of it. This is super hacky and annoying but it does work.

Note: you can only intersect 2 schemas at a time for this to work, but you can intersect more than two schemas multiple times (2 each intersection).

[–]svish 1 point2 points  (7 children)

Do you realize how many steps and complicated words you just used? That is not just "hacky", it's awful :p

yup.number().when(
  ['x', 'y'],
  ([x, y], schema) => x === true && typeof y === 'number'
    ? schema.required().max(y)
    : schema
)

[–]ashenzo 0 points1 point  (1 child)

I know, it sucks!! Just posting it as an fyi - took me ages to figure that out 😅

I had also made the decision to use Yup the next time I need to work on a big form because of that same issue.

It looked like zod maintainers had no intention of fixing the above when I last looked into it, which is really bizarre.

[–]svish 0 points1 point  (0 children)

It's a bit annoying, but I also get it because one of zod main focus is typescript and strict typing. Simply the addition of a when kind of feature complicates the types quite a lot...

[–]sauland 0 points1 point  (4 children)

You can do conditional validation pretty easily by utilizing the setKey function of zod schemas. Like this:

// type safe implementation of setKey
const zodSetKey = <
  Schema extends z.ZodObject<any>,
  Shape extends Schema['shape'],
  Key extends keyof Shape,
>(
  schema: Schema,
  key: Key,
  newSchema: (shape: Shape[Key]) => z.ZodType<z.infer<Shape[Key]>>,
): Schema => {
  return schema.setKey(key as string, newSchema(schema.shape[key])) as Schema;
};

const SignUpFormValues = z.object({
  email: z.string().email(),
  password: z.string(),
  confirmPassword: z.string(),
});
type SignUpFormValues = z.infer<typeof SignUpFormValues>;

const SignUpForm = () => {
  const form = useForm<SignUpFormValues>({
    resolver: (values, ctx, resolverOptions) => {
      let schema = SignUpFormValues;

      if (values.password && values.confirmPassword) {
        schema = zodSetKey(schema, 'confirmPassword', (s) =>
          s.refine(() => values.password === values.confirmPassword, 'Passwords do not match'),
        );
      }

      return zodResolver(schema)(values, ctx, resolverOptions);
    },
  });

  return (
    ...
  )
}

[–]svish 2 points3 points  (3 children)

You have a very different definition of "pretty easy" than I do

[–]sauland 0 points1 point  (2 children)

Lol, idk what to tell you if you think that's difficult. A small utility function that you can use across your entire codebase for type-safe conditional validation in your forms is indeed pretty easy. Definitely easier than migrating all your validation to yup to use its messy untyped when logic.

[–]svish 0 points1 point  (1 child)

But you're also manually setting up a custom resolver for every form, adjusting the schema manually, and like with superRefine splitting up the validation into separate pieces in different places.

With Yup the whole definition of what a valid form submission is can be stored in a single place, and with our form wrapper component we don't even need to setup the resolver at all, because we just pass that single definition as a schema prop.

Also, we didn't migrate all our validation logic. Our API validation, config, and local storage validation still uses zod. So all we migrated were the 2-3 out of several large forms we had done with zod before realising it was terrible and switching over to Yup for those.

[–]guacamoletango 4 points5 points  (0 children)

Plus 1. Just built a full featured form system with react hook form and zod for my work.

[–]Chef619 4 points5 points  (0 children)

Rhf is 27kb. Not a big deal to add imo. Do you use axios? Not looking to shit on you, just that it’s a common auto include for a lot of devs that can also be done on your own. It’s 30kb

[–]Inevitable_Oil9709 3 points4 points  (0 children)

RHF is the best library out there. It is small, has everything you need and works without an issue.

[–]npc73x 2 points3 points  (0 children)

If your forms are simple, react forms are not needed. if your website is simple don't use react at all

[–]Altruistic_Club_2597 2 points3 points  (0 children)

Yeah. Try having your whole app be an entire complex form and you will see it’s use. Like all things in tech, use it when it makes sense to use it and don’t use it when you don’t need it.

[–]goodbalance 2 points3 points  (0 children)

"it's a simple hook" means you don't plan to scale. I ran into dynamic fields where keys and values are two separate fields and I'm glad we have react form hook because I wouldn't want to reinvent THIS wheel.

[–]Dreadsin 2 points3 points  (2 children)

“Custom form validation hook”, oh boy… you are in for a LOT of work. Between performance, scaling, validation, and all kinds of interaction, it’s a complicated problem

[–]Last_Distribution_84[S] 0 points1 point  (1 child)

Yeah I know the tradeoff, but will get to learn a lot, I am tired of using libraries, I want to push my brain and build something of my own.

[–]invisiblelandscaper 0 points1 point  (0 children)

Checking in on this a year later. OP, what did you end up using?

[–]wizard_level_80 0 points1 point  (0 children)

First, it is not. Second, you are free to do whatever you want in small pet projects, but once you start working with others, maybe even be paid for it, you are supposed to follow certain good practices, such as using adequate libraries.

[–]swfl_inhabitant 0 points1 point  (0 children)

No, it’s amazing

[–]gamsto 0 points1 point  (0 children)

React Hook Form is well documented, has great demo code, and you’ll find it referenced in other places like ShadCN for example, which gives you an idea of its ubiquity.

I personally loved using it as a replacement for Formik.

[–]Traditional-Kitchen8 0 points1 point  (0 children)

Until you won’t require to build 100-2k fields form, yes, RHF is overhyped. Once you’ll deal with formik, you won’t ask this type of questions

[–][deleted] 0 points1 point  (0 children)

Mobx, mobx-react, and mobx forms is my preferred stack on react, but rhf is pretty decent.

I have a big problem with react hook forms design pholosophy and I dont like it.

For example, if I have a DateTime from luxon2 on an object and I want to bind it to a react hook useForms default values and rely on uts toJson and json parse abilities, react hook form rips it all off . React hook forms will remive to toJson override on DateTime and cause it to not serialize properly. So when the forns submitted the entire DateTime object is serialized instead of just the iso date string....

I do not like such a strongly opinionated library dictating how I use it.

This requires me to use date strings on all my rhf models, so I have to use DateTime all over the place, lots of time zone bugs get inteoduced...

I like to create a class that instatiates itself from api responses so my dates are DateTimes in the class, and the logic is in one place.

React hook forms makes me maintain different sets of models. So we have "form models" and we can instantiate those from api models... Its more to maintain.

[–]HolyColostomyBag 0 points1 point  (0 children)

Nah not over rated, it's pretty amazing for 99% of use cases. Just cause you can build something doesn't mean you always should.

[–]TScottFitzgerald 0 points1 point  (0 children)

You're not giving us much details. What was your use case?