Home > Net >  React Typescript - render function not applying types correctly
React Typescript - render function not applying types correctly

Time:05-05

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;

Playground link

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.

Playground

  • Related