The data type is incorrect. The FieldName
type will always be evaluated as a string
, regardless of the input provided. Here are a few examples to consider:
type X = FieldName<{ x: string }> // X is string
type Y = FieldName<{y: number, z: string}> // Y is string
type Z = FieldName<{w: boolean, q: string}> // Z is string
The problem lies in the union part:
export type FieldValues = Record<string, any>; // object type with string keys
export type FieldName<FormValues extends FieldValues> =
| (keyof FormValues & string) // intersecting with string
| string; // everything becomes string due to this line
If we want to specify a particular type, the final line | string
negates the first line. Since keyof FormValues
is a subset of string
, the union results in just a string
. In reality, the type should be defined as follows:
export type FieldName<FormValues extends {}> = keyof FormValues & string
type A = FieldName<{ x: string }> // A is "x"
type B = FieldName<{ m: number, n: string }> // B is "m" | "n"
type C = FieldName<{o: boolean, p: string}> // C is "o" | "p"
type D = FieldName<{1: string}> // D is never - accurate
Note that I no longer use FormFields
since & string
ensures that our key is strictly a string. Types from A to D consistently narrow down the result types, and for types with numeric keys, we get never
as expected, considering only string
keys.