Consider the function f, which is defined as follows:
function f<T extends Fields = Fields>(props: Props<T>) {
return null;
}
In this function, T represents a generic type that extends Fields, with Fields defined as:
export type Fields = { [key: string]: unknown };
Furthermore, the Props interface is defined as:
export interface Props<T extends Fields = Fields> {
fields: Config<T>;
onSubmit?: (values: Values<T>) => void;
}
Here, Props accepts a generic type T that extends Fields, and it consists of two properties: fields and onSubmit. The fields property is of type Config<T>, and the onSubmit property is an optional function that takes values of type Values<T> and returns void.
To provide more context, Config and Values are defined as follows:
type BaseProps<T> = {
initialValue: T;
hidden?: boolean;
};
export interface TextInput extends BaseProps<string>, TextInputProps {
type: 'text';
}
export interface Checkbox extends BaseProps<boolean> {
type: 'checkbox';
}
type Config<T> = { [K in keyof T]: TextInput | Checkbox };
export type Values<T extends Fields> = {
[K in keyof T]: Config<T>[K]['initialValue'];
};
Here, Config<T> represents a mapped type where each key in T is mapped to either a TextInput or Checkbox. On the other hand, Values<T> represents a mapped type where each key in T is mapped to the initial value of the corresponding field in Config<T>.
In summary, the function f expects props of type Props, which contains information about the form fields (fields) and an optional submit function (onSubmit). The fields property is defined using a mapped type (Config<T>), and the initial values of these fields are extracted using another mapped type (Values<T>).
The core question here is whether there’s a method for Values to automatically infer the correct type. Presently, the values.age type is inferred as string | boolean
. This ambiguity stems from the use of the ‘or’ operator within Config, which allows for either a TextInput or Checkbox.
The concern is not merely about the technical aspect but also about the design implications. Is there a structural or architectural adjustment that can lead to more precise type inference? Or is this ambiguity inherent in the design and therefore acceptable?
In essence, we’re exploring whether there’s a way to refine the type inference mechanism to accurately determine the type of values.age.
f({
fields: {
name: {
type: 'text',
initialValue: 'John Doe',
},
age: {
initialValue: true,
type: 'checkbox',
},
},
onSubmit: (values) => {
console.log(values.age);
},
});
I’ll provide the entire content here for convenient copying.
type BaseProps<T> = {
initialValue: T;
hidden?: boolean;
};
export interface TextInput extends BaseProps<string> {
type: 'text';
}
export interface Checkbox extends BaseProps<boolean> {
type: 'checkbox';
}
type Config<T> = { [K in keyof T]: TextInput | Checkbox };
export type Fields = { [key: string]: unknown };
export type Values<T extends Fields> = {
[K in keyof T]: Config<T>[K]['initialValue'];
};
export interface Props<T extends Fields = Fields> {
fields: Config<T>;
onSubmit?: (values: Values<T>) => void;
}
function f<T extends Fields = Fields>(props: Props<T>) {
return null;
}
f({
fields: {
name: {
type: 'text',
initialValue: 'John Doe',
},
age: {
initialValue: true,
type: 'checkbox',
},
},
onSubmit: (values) => {
console.log(values.age);
},
});
I experimented with several approaches to infer that, but unfortunately, none of them proved successful.