Understanding useForm Type Declarations in React Hook Form
Let's start by looking at
useForm's type definition:
export function useForm<TFieldValues extends FieldValues = FieldValues,TContext = any,TTransformedValues extends FieldValues | undefined = undefined>(props?: UseFormProps<TFieldValues, TContext>): UseFormReturn<TFieldVal
00:00 Let's take this step-by-step. UseForm here, we're going to command click on it, and this is going to be pretty scary to start with. What we end up with here is a big old, huge, great big declaration file. We're in useform.d.ts, which it looks like is part of types or part of actually
00:19 the type declarations which ship with React hook form. I'm going to actually unwrap this, so un-word wrap it so we can see everything. We have here three type arguments which go into useform. We've got tFieldValues, tContext, and tTransformedValues. If we take another look at our useform here,
00:37 we can see that actually only the first one actually is being used when we call it like this. We have firstName and lastName. The other one is typed as any, the other one is typed as undefined. If we look at that here, we can see that tContext has a default of any and a default of undefined for tTransformedValues.
00:56 You can essentially ignore these ones just for our purposes here. tFieldValues is the only one we care about. It looks like it defaults to field values if it can't infer anything. We take a look at useform, and actually we look at the second one here.
01:11 This is why when it's not inferring anything, or when it can't infer anything, the default thing that it infers is field values. Let's take a quick look at the field values type then. Field values type is a record string any,
01:30 which is extremely loose here. This, I think, is a sensible decision for them to make. Because if you don't know what type something is, then actually having field values as the default type, this form could technically be called with anything. It could be like, we could actually make this stricter if we wanted to.
01:48 We could say, if I were the designer of React hook form, for instance, I would say, unless you actually specify what type this is, make sure it's like an empty object or something. But that wasn't the decision they made. They've created quite an open type here. That's what field values means, and that's what happens when you
02:08 don't pass any default values to it. But how is this even working, this inference of first name and last name being inferred in that slot? Let's dive into that. tFieldValues, where is that coming from? What slot is that being inferred from? Well, we've got these props here, and the props are what you pass into useForm.
02:27 You notice that you don't actually have to pass it anything. These useForm props here, it takes in tFieldValues. The type that's being inferred here or this type argument gets passed to this generic type too. Let's take a look here then. Dive into useForm props. Hello, big old file.
02:45 We've got now, this is a partial wrapping this object here, and this object is all of the things that you can pass to useForm props. Partial default values, and default values, it looks like it's taking in tFieldValues here,
03:01 so either async default values or default values, and passing in tFieldValues. If we look at default values here, it's a deep partial of tFieldValues. We're now at the point where we understand, okay, what's tFieldValues? It's now, okay, it's got some utils here,
03:19 and deep partial is basically making a deep copy of that thing, but making each member of it, each member of the object optional. We've got then, deep partial of tFieldValues is default values here, and we go back to where we were, that's on the default values property.
03:39 Anything passed to default values, that will be inferred as tFieldValues, and then we pass it all the way up here, and that's on the props of useForm. That's how it works. By specifying default values, TypeScript understands that that is what it's supposed to infer in this particular slot.
03:58 Amazing. Very, very cool. This is how it often works with external libraries. You're going to be diving deep into something to actually work out the inference site of where something is inferred from. Now, how do we actually get it to play nice with our stuff here?
04:16 The solution is to pass it a type argument of the thing that we want it to infer. If we don't pass this, useForm just sits like this. We know that when we have a type argument in a function, we can either just let it infer from its inference sites like it's doing above,
04:36 or we can pass it something. That's something we're passing is our form values. This is what React Hook form recommends in its docs too. Now, what we get is even though we haven't passed in anything here because this thing is a partial, this props thing here, so you don't actually have to pass in anything here,
04:53 and it's even got an optional parameter here too. You actually can just do the inference from there. Register now works properly. We've got firstName and lastName here, and this values is typed as these form values. To sum up, useForm takes in T field values, which is the type that's being inferred there.
05:13 If you don't pass in the default values here, then it's going to just infer field values, which is a really big wide open type, just record string any. The way that you can get it to infer properly in this situation is by passing it a type.
05:28 Now, this information is going to stand us in good stead for the next exercise, and also hopefully the techniques that we've learned about diving in and understanding where all the inference sites are, they will also help us whenever we're looking at some third-party libraries in the future.