The error "Type 'undefined' is not assignable to type 'number'"
occur when we try to assign a potentially undefined value to a string or number variable. In the case of a string variable, the error reads as "Type 'undefined' is not assignable to type 'string'"
In Typescript, when you define a variable with the type of string or number, only the defined type can assign to that variable. It is important to note that these type check errors happen when you have the strict mode turned on by setting "strict": true
in the tsconfig.json
file.
How to type-check optional variables
Let's take a look at a simple example. My interface PersonId
defines the id as optional.
interface PersonId {
id?: number
name: string
}
export const Person = (props: PersonId) =>{
const ids: number[] = []
ids[0] = props.id // Type 'number | undefined' is not assignable to type 'number'
}
Now I am trying to insert the id
variable into a number array. But Typescript is smart enough to figure that the id variable may potentially be undefined. My ids
array only accepts numbers.
Solution 1
I can check the id
to see if it is not undefined
and then assign the value to the array.
!!props.id && (ids[0] = props.id)
Solution 2
Doing the same thing, but with better readability.
if(!!props.id){
ids[0] = props.id
}
Solution 3
I can use the non-null
assertion operator to convince Typescript that my id cannot be null. However, I consider this a workaround, not a good practice. That is not a guaranteed fix, as Eslint can restrict using non-null
assertions. Anyways, here is how to do that.
ids[0] = props.id!
The above solutions work on a single variable at a time. What if I have an array of objects that I want to convert into a number or string array?
Object array to string or number array with type safety
Now I receive a very long array of PersonId
objects. Here is what my object array looks like.
const idList: PersonId[] = [
{id: 100, name: "Smith"},
{id: 101, name: "John"},
{id: 102, name: "Satoshi"},
{id: 103, name: "Ravi"}
]
The array is a type of PersonId
, where the id
is optional.
interface PersonId {
id?: number
name: string
}
My hardcoded array guarantees to have all the id variables filled. But Typescript only cares about what the interface guarantees, and the interface does not guarantee the id
. It is optional.
interface PersonId {
id?: number
name: string
}
export const Person = (props: PersonId[]) =>{
const idList: PersonId[] = [
{id: 100, name: "Smith"},
{id: 101, name: "John"},
{id: 102, name: "Satoshi"},
{id: 103, name: "Ravi"}
]
const idArray: number[] = idList.filter((list) => // Undefined error in this line
list.id !== undefined
).map((id) => id.id)
console.log(ids1)
}
In the above code, I filter out the idList array to remove the undefined
id's and then collect the id's with values to build the number array idArray
. But Typescript is not convinced that the map returns only defined ids. I have to tell Typescript explicitly that the map function returns only defined values.
Solution 1
I can use the non-null
assertion operator to convince Typescript that the map function only returns defined values. Again, that is not recommended.
const idArray: number[] = idList.filter((list) =>
list.id !== undefined
).map((id) => id.id!)
Solution 2
I can use the Required
type to hint at Typescript that my filter function guarantees to discard undefined values.
const idArray: number[] = idList.filter(
(list: PersonId): list is Required<PersonId> =>
list.id !== undefined
).map((id) => id.id)