I have a minimal reproducible example of the problem at the following link:
import React, { useState } from 'react';
type InputValue = string[] | string;
interface InputProps<T extends InputValue> {
value: T;
}
// -------------------------------------------------------------------------------------------------
// TEXT INPUT
// -------------------------------------------------------------------------------------------------
const TextInput = ({ value = '' }: InputProps<string>): React.ReactElement => {
return <input value={value} />;
};
// -------------------------------------------------------------------------------------------------
// FORM FIELD
// -------------------------------------------------------------------------------------------------
interface FormFieldProps<T extends InputValue> {
render: (props: InputProps<T>) => React.ReactElement;
value: T;
}
const FormField = <T extends InputValue>({
render,
value,
}: FormFieldProps<T>): React.ReactElement => (
<div>
{render({ value })}
</div>
);
// -------------------------------------------------------------------------------------------------
// FORM
// -------------------------------------------------------------------------------------------------
interface FieldPropsReturn<T> {
value: T[keyof T];
}
interface UseFormReturn<T> {
fieldProps: (name: keyof T) => FieldPropsReturn<T>;
}
const useForm = <T extends object>(): UseFormReturn<T> => {
const [formData] = useState<T>({} as T);
const fieldProps = (name: keyof T): FieldPropsReturn<T> => ({
value: formData[name],
});
return {
fieldProps
};
};
// -------------------------------------------------------------------------------------------------
// INDEX
// -------------------------------------------------------------------------------------------------
interface IndexFormFields {
fruits: string[];
name: string;
}
const Index = (): React.ReactElement => {
const { fieldProps } = useForm<IndexFormFields>();
return (
<FormField
{...fieldProps('name')}
render={(props): React.ReactElement => <TextInput {...props} />}
/>
);
};
export default Index;
Essentially, I am building some form helpers, including a FormField
that renders a form component. I would like these FormField
render functions to infer the value type from the key passed to fieldProps
, but at the moment it is returning the following error on the TextInput
component:
Type '{ value: string | string[]; }' is not assignable to type 'InputProps<string>'.
Types of property 'value' are incompatible.
Type 'string | string[]' is not assignable to type 'string'.
Type 'string[]' is not assignable to type 'string'.
Is there any way that a render function for a FormField
with a fieldProps
of 'name' will pass a value type of string
, and one with a fieldProps
of 'fruits' will pass a value type of string[]
, as per the IndexFormFields
interface?
CodePudding user response:
Add a generic to fieldProps
, similar to this answer:
const fieldProps = <Name extends keyof T>(name: Name): FieldPropsReturn<T, Name> => ({
value: formData[name],
});
You would also need to update the interface to reflect this:
interface FieldPropsReturn<T, K extends keyof T = keyof T> {
value: T[K];
}
The return type on useForm
is also not needed, but if you must, you have to also change the type of fieldProps
there.
Now when you use your component, you get no errors. And if you use it incorrectly, you get errors.