Home > database >  Reusable Form components in react hook form with typescript
Reusable Form components in react hook form with typescript

Time:09-07

I'm trying to get typescript working with fields that can belong to different forms. I'd like the Field to define the fields that are needed in the form, so that it can be reused. I've currently used Control<any> to circumvent the issues, but that limits typescript utility. The error that I'm getting is

  The types of '_options.resolver' are incompatible between these types.
    Type 'Resolver<IFormAncestor, any> | undefined' is not assignable to type 'Resolver<DepForm, any> | undefined'.
      Type 'Resolver<IFormAncestor, any>' is not assignable to type 'Resolver<DepForm, any>'.

44             <TestField control={control} trigger={trigger} getValues={getValues} />
  Type 'DepForm' is not assignable to type 'IFormAncestor'.

44             <TestField control={control} trigger={trigger} getValues={getValues} />
interface IFormAncestor {
    a: string;
    b: string;
    c: string;
}

interface DepForm {
    a: string;
    b: string;
}


export const TestField = (props: {
    control: Control<DepForm>;
    getValues: UseFormGetValues<DepForm>;
    trigger: UseFormTrigger<DepForm>;
}) => {
    props.getValues();
    props.trigger();
    return (
        <Controller
            name="a"
            control={props.control}
            defaultValue={''}
            render={({ field }) => {
                return (
                    <div>{field.value}</div>
                );
            }}
        />
    );
};

function TestingAncestorForm() {
    const { control, trigger, getValues } = useForm<IFormAncestor>({
        mode: 'onBlur',
    });
    return (
        <div>
            <TestField control={control} trigger={trigger} getValues={getValues}/>
        </div>
    );
}

Is there a way to get this working? Cheers!

CodePudding user response:

So after Chris'es answer I've come up with the following.

interface DependantForm {
    a: string;
    b: string;
}

export const DesiredValueField = <T extends DependantForm>(props: {
    control: Control<T>;
    getValues: UseFormGetValues<T>;
    trigger: UseFormTrigger<T>;
}) => {
    const control = props.control as unknown as Control<DependantForm>;
    const getValues = props.getValues as unknown as UseFormGetValues<DependantForm>;
    const trigger = props.getValues as unknown as UseFormTrigger<DependantForm>;

CodePudding user response:

Using a generic which extends DepForm will do it here. I had to assert the name and defaultValue values, which shouldn't be a practical problem since you know they should be valid in this context, but you might be able to work around that with a bit more fiddling too.

function TestField<T extends DepForm>(props: {
    control: Control<T>;
    getValues: UseFormGetValues<T>;
    trigger: UseFormTrigger<T>;
}) {
    props.getValues();
    props.trigger();
    const name : Path<DepForm> = "a";
    const defaultValue : PathValue<DepForm, Path<DepForm>> = "";
    return (
        <Controller<T>
            name={name as Path<T>}
            control={props.control}
            defaultValue={defaultValue as PathValue<T, Path<T>>}
            render={({ field }) => {
                return (
                    <div>{field.value}</div>
                );
            }}
        />
    );
};

Playground link

  • Related