I'm trying to create a form component in React that uses generic types.
The generic type for the form is the initialValues that are passed in.
I can't for the life of me seem to get Typescript to narrow down the type that corresponds to a particular field.
Here's a CodeSandbox with one of my many attempts at trying to get this to work correctly.
I've tried many other things, like adding a 'type' property to each of the fields and then using a switch statement to try and narrow it down that way, but none of them have seemed to work!
Not looking anyone to write this code for me, but a nudge in the right direction would be greatly appreciated.
So as you've probably guessed by now, to do this you can't just define an indexable type like
type FormValues<T> = {
[key: string]: T
}
Because T
in this case will be a union that doesn't help too much.
So you can't use indexable type, now the only way I can think stop trying to create the type yourself and just let typescript infer it for you.
Firstly let me change your FormValue
declaration a bit, I'll remove ValueOf
, while I was playing with your sandbox I forgot what was its purpose, but if I don't think it matters in the way I'm suggesting. So let's type it like this:
type FormValue<TName extends string, TValue> = {
value: TValue
name: TName
pristine: boolean
errors: string[] | null
}
Now this will just take two type arguments, one is the name, another is the value, no unnecessary objects. Now let's type InitialFormValues
: it will be just an object with random properties, we won't use it to type anything, only to constraint types:
type InitialValues = {
[key: string]: any
}
// or
type InitialValues = Record<string, any>
Next comes UseFormProps
. Here we avoid creating indexable type, that doesn't lead to anything, but we let typescript infer the type for us. We only need to constraint it a bit so that it's not possible to pass pe a number as initialValues
:
type UseFormProps<T extends InitialValues> = {
initialValues: T
}
Now typescript will automatically choose T
and it will be exactly what we need: if initialValues
is { foo: 1, bar: 'str' }
, it will be { foo: number, bar: string }
and nothing more.
Last goes FormValues
:
type FormValues<T extends InitialValues> = {
[x in keyof T]: FormValue<x, T[x]>
}
Now FormValues
will not be just indexable, but it will contain exactly the properties T
contains, also T[x]
will not be a union like T[keyof T]
, it will be different for each value of x
so for each property. That's basically it, now it should work as expected. You'll only need to change something in transformInitialValues
, because typescript will now complain if you assign empty object to FormValues<whatever>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.