Home > OS >  How to fix "Type 'string | boolean' is not assignable to type 'never'. Type
How to fix "Type 'string | boolean' is not assignable to type 'never'. Type

Time:05-11

interface IData {
    title: string;
    slug: string;
    published_at: string;
    isPopular: boolean;
}

const product: IData = {
    title: "Title 1",
    slug: "title-1",
    published_at: "2012",
    isPopular: true,
};

function getCourseBySlug(fields: Array<keyof IData>) {
    const items = <IData>{};
    // // Ensure only the minimal needed data is exposed
    fields.forEach((field) => {
        if (field === "isPopular") {
            items[field] = product.isPopular;
        }
        if (typeof product[field] !== "undefined") {
            items[field] = product[field]; // I am getting error here items[field]
        }
    });
    return items;
}

I am getting the error at "items[field]". I already tried a few solutions to other similar questions. But nothing worked for me. What I am missing here? Error message: Type 'string | boolean' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'

CodePudding user response:

The issue is because items is an empty object so items[field] will "never" exist

switching to a reduce method will both solve the error (since you are not trying to access a value that doesn't exist) and cleans up your function slightly

function getCourseBySlug(fields: Array<keyof IData>): IData {
    return fields.reduce((acc: IData, field: keyof IData) => {
        if (typeof product[field] !== "undefined") {
            return { ...acc, [field]: product[field] };
        } else {
            return acc;
        }
    }, {
        title: "",
        slug: "",
        published_at: "",
        isPopular: false,
    });
}

--edit--

if you want your returned object to be a subset of the original as pointed out in comments then you can replace the return and accumulator types with Partial

function getCourseBySlug(fields: Array<keyof IData>): Partial<IData> {
    return fields.reduce((acc: Partial<IData>, field: keyof IData) => {
        if (typeof product[field] !== "undefined") {
            return { ...acc, [field]: product[field] };
        }
    }, {});
}

CodePudding user response:

There are two distinct issues:

  1. Your items is not a valid IData object. It doesn't have all of the required IData properties.

  2. In your loop, TypeScript doesn't know the specific type of field (just that it's one of keyof IData) and so doesn't know whether items[field] is string or boolean; as a result, you can't assign to it. Even though you know that items[field] and product[field] will have the same type, TypeScript doesn't.

The first issue is simple: Define it as a partial IData:

const items: Partial<IData> = {};

You have at least three options for fixing #2:

  • Make the function fully typesafe with a helper.

    function copyProp<ObjType extends object, KeyType extends keyof ObjType>(obj: ObjType, key: KeyType, value: ObjType[KeyType]) {
        obj[key] = value;
    }
    function getCourseBySlug(fields: Array<keyof IData>) {
        const items: Partial<IData> = {};
        for (const field of fields) {
            copyProp(items, field, product[field]);
        }
        return items;
    }
    

    That works because copyProp ensures that the value passed in (product[field]) is type-compatible with the target object's field via ObjType[KeyType].

    Playground link

  • Tell TypeScript to ignore the error. This is rarely the right thing to do, but sometimes in cases where you know the error isn't important, it's not unreasonable.

    function getCourseBySlug(fields: Array<keyof IData>) {
        const items: Partial<IData> = {};
        for (const field of fields) {
            // @ts-ignore
            items[field] = product[field];
        }
        return items;
    }
    

    Playground link

  • You could do an end-run around it by hiding the issue behind Object.fromEntries:

    function getCourseBySlug(fields: Array<keyof IData>) {
        const items: Partial<IData> = Object.fromEntries(fields.map(field => [field, product[field]]));
        return items;
    }
    

    Playground link

The first of those is fully typesafe (I think). The second two are both imperfect, but in a very contained way. getCourseBySlug is a small function and within it you know that items[field] and product[field] have the same type.

Note that in all of those, I removed your typeof product[field] !== "undefined" test. There's no reason for it, the type of fields ensures that field is a valid key for product, and none of IData's properties is optional.

  • Related