DEV Community

Cover image for Type Widening Is Kinda Weird
Dan Fletcher
Dan Fletcher

Posted on

Type Widening Is Kinda Weird

Do you notice how in TypeScript that when you initialize a variable to a boolean, string or number, that TypeScript infers the type to be exactly that? A boolean, string or number.

For example:

let a = 4
a = 5
a // is 5 since type is number not 4
Enter fullscreen mode Exit fullscreen mode

💡 Keep in mind, if you use const it prefers the more specific type 4

This is a huge feature of TypeScript! Type Inference is essential to writing easy to read, and maintainable TS codebases. It removes a lot of verbosity as well as reduces the amount of type code that has to be updated when a type upstream changes.

However there are some times, where this can cause weird errors. For example, I didn’t guess at first glance that the below code would cause a type error:

type User = {
  kind: 'user'
  name: string
};

function userName(user: User) {
  return user.name;
}

const user = {
  kind: 'user',
  name: 'Jane',
};
Enter fullscreen mode Exit fullscreen mode

Can you spot the error?

If you caught it, great! If not, here's the issue you'll see from TypeScript:

Argument of type '{ kind: string; name: string; }' is not assignable to parameter of type 'User'.
  Types of property 'kind' are incompatible.
    Type 'string' is not assignable to type '"user"'.
Enter fullscreen mode Exit fullscreen mode

Essentially what's happening is kind gets widened to type string when creating the user object, but the function expects kind to be type 'user'.

Solution

So what's the solution?

Use as const:

const user = {
  kind: 'user' as const,
  name: 'Jane',
};
Enter fullscreen mode Exit fullscreen mode

When we use as const it tells TypeScript that we don't want the inference to prefer the wider type of string but instead use the more narrow type 'user'. And in this case, our error above goes away!


Like what you read? Want to support me?

A cup of coffee goes a long way 🙏

Why not buy me one? https://ko-fi.com/danjfletcher

Oldest comments (2)

Collapse
 
thediamondcg profile image
Diamond

You are better off using something like

{
  kind: 'employee' | 'user',
  name: string
}
Enter fullscreen mode Exit fullscreen mode

or

{
  kind: string,
  name: string
}
Enter fullscreen mode Exit fullscreen mode

because it better communicates code intent. If I saw the User type as it was in your blog post, I'd be very confused as to why I'm seeing a field that can only ever be 'employee'.

Collapse
 
danjfletcher profile image
Dan Fletcher • Edited

Oh you caught a typo! Let me fix that. It should be 'employee' or 'user' everywhere in these examples.

The example I was pulling from, instead of the union though, is like:

type Employee = {
  kind: 'employee',
  name: string
}

type Admin = {
  kind: 'admin',
  name: string
}

type User = Employee | Admin
Enter fullscreen mode Exit fullscreen mode

Where kind is the discriminator, if that makes sense