Search code examples
reactjstypescripttypescript-typingsreact-typescriptconditional-types

Typescript: If multiSelect is true, then I want to change the types


currently I have some troubles with typescript. I have a React component, where some typescript definitions should change, when multiSelect is true. onUpdate and value will be forced to a string OR string[] when multiSelect is true or false. But is doesn't work.

export interface DefaultUserTypeFieldProps {
    className?: string;
    disabled?: boolean;
    /**
     * The label of the input
     */
    label?: string;
    autoSelectUserId?: string;
}

export interface SingleUserTypeFieldProps extends DefaultUserTypeFieldProps {
    multiSelect: false;
    value: string;
    onUpdate: (value: string, isValid: boolean) => void;
}

export interface MultipleUserTypeFieldProp extends DefaultUserTypeFieldProps {
    multiSelect: true;
    value: string[];
    onUpdate: (value: string[], isValid: boolean) => void;
}

export type UserTypeFieldProps = SingleUserTypeFieldProps | MultipleUserTypeFieldProp;

The React component looks like this

export const UserTypeField = (props: UserTypeFieldProps) => {
const {
    className,
    label,
    disabled,
    value,
    autoSelectUserId,
    multiSelect = false,
    onUpdate,
} = props;

const handleSelectUser = (selectedUserIds: string[]) => {
    close();
    if (multiSelect) {
        onUpdate(selectedUserIds, true);
    } else {
        onUpdate(selectedUserIds[0], true);
    }
};

return ...;

};

In handleSelectUser I get the error TS2345: Argument of type 'string' is not assignable to parameter of type 'string & string[]'. Type 'string' is not assignable to type 'string[]'.. As you can see, it is connected with a & but in the interface definition you can see that I use conditional types with |. Do you have any ideas ?

Thank for your help!


Solution

  • The problem is in destructuring. TS has problems with it :)

    export interface DefaultUserTypeFieldProps {
        className?: string;
        disabled?: boolean;
        /**
         * The label of the input
         */
        label?: string;
        autoSelectUserId?: string;
    }
    
    export interface SingleUserTypeFieldProps extends DefaultUserTypeFieldProps {
        multiSelect: false;
        value: string;
        onUpdate: (value: string, isValid: boolean) => void;
    }
    
    export interface MultipleUserTypeFieldProp extends DefaultUserTypeFieldProps {
        multiSelect: true;
        value: string[];
        onUpdate: (value: string[], isValid: boolean) => void;
    }
    
    export type UserTypeFieldProps = SingleUserTypeFieldProps | MultipleUserTypeFieldProp;
    
    
    
    export const UserTypeField = (props: UserTypeFieldProps) => {
    // problem is here
        const {
            className,
            label,
            disabled,
            value,
            autoSelectUserId,
            multiSelect = false,
            onUpdate,
        } = props;
    
        const handleSelectUser = (selectedUserIds: string[]) => {
            close();
            if (props.multiSelect) {
    // here is the fix
                props.onUpdate(selectedUserIds, true);
            } else {
                props.onUpdate(selectedUserIds[0], true);
            }
        };
    
        return null
    }
    

    Playground

    Just use props.onUpdate instead of onUpdate.

    I believe such behaviour is logged in gtihub issues but unable to find a link

    UPDATE

    If it still don't work, please enable strictNullChecks Thanks @Roberto Zvjerković for the tip!