TypeScript: 'number | undefined' is not assignable to parameter of type 'number'

Viewed 5670

I have a model with Activiy Interface and Mongo Schema, and a function that takes an ID and logs it. Then in my route I'm querying all activities from a given customer where the externalId is not undefined. With the returned array I call that function defined in my model, passing externalId as param.

But TypeScript keeps throwing: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. Type 'undefined' is not assignable to type 'number'. I already tried to wrap the call with an if statement checking if externalId && typeof externalId === 'number', but I can't make the error go away.

Model:

import { Schema, model, } from 'mongoose';

interface IActivity {
    customer: string,
    externalId?: number,
}

const activitySchema: Schema = new Schema({
    Customer: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 50,
    },
    externalId: { type: Number, },
});

export function updateExternal(externalId: number): void {
    console.log(externalId);
}

export const Activity = model<IActivityModel>('Activity', activitySchema);

Route:

const completeActivity = await Activity
    .find({
        'externalId': { '$ne': undefined, },
        'customer': 'Foo Customer',
    });

completeActivity.forEach((activity) => {
     updateExternal(activity.externalId); // This is where the error appears
});
1 Answers

It's interesting to think what should happen in order to get the value at a key, when the key may not really have a value. activity.externalId has type number | undefined so what to we do in order to move from number | undefined to number?

We need to 'remove' that part of the type, that is to say, we need to provide some way to convert all the undefined parts of number | undefined to number.

Often we do this by providing some default - we say that whenever a value is undefined, don't use it - use the default instead. It is difficult to get a good type for this with typescript, though; we can't specify that the type of the object has the property we're looking for; so we must simply say that it can be any, and as such, the function would only be able to return any, as in:

interface IX {
    a?: number;
}

function propOr<K extends string | number, U, O extends Record<K, U>>(k: K, defaultValue: U, obj: O) {
    return obj[k];
}

const testFunction1 = (x: IX) => x.a;                // has type number | undefined
const testFunction2 = (x: IX) => propOr('a', -1, x); // type error: 

The type error we get here is:

Argument of type 'IX' is not assignable to parameter of type 'Record<"a", number>'. Types of property 'a' are incompatible. Type 'number | undefined' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.(2345)

We might look at other libraries, to see what they do, Sanctuary, for instance, has it as:

prop(p: string): (q: any) => any;

So when typing a function, we're a little stuck, we're forced to lose the type information (unless someone else can jump in with a better idea).

The best solution I can think of, is to just use an or to always get a number:

interface IX {
    a?: number;
}

const a: IX = {};
const b = a.a || 0;

Now b has the type number.

Related