Beginner's TypeScript Tutorial (18 exercises)
solution

Delineate Types a Value Can Be

The solution is to update role to be a Union type.

The syntax uses | to delineate the options for which values a key can be:

interface User {
  id: number;
  firstName: string;
  lastName: string;
  role: "admin" | "user" | "super-admin";
}

With this change, removing the // @ts-expect-error line will show us the error message along with autocomplete for the allowed options.

About Union Types

Anything can be added to a union type. For example, we can make a new SuperAdmin type and add it to the role:

type SuperAdmin = 'super-admin'

interface User {
  // ...other stuff
  role: "admin" | "user" | SuperAdmin;
}

In this example, an object primitive is added, and Prettier reformats the code like so:

role:
  | "admin"
  | "user"
  | SuperAdmin
  | {
      wow: boolean;
    }

Union types are everywhere within TypeScript, and will be used throughout Total TypeScript.

They give your code safety and allow you to be really sure of the types of data flowing through your app.

Transcript

Here we go. This is a union type, which means that role can either be admin, user, or super-admin. We use this syntax here to delineate which ones it can be. Now, if I remove that ts-expect-error, I'm going to get autocomplete inside this role, and I access the autocomplete by going control space. At least that's for me on a Mac using VS Code. Your mileage may vary.

We have admin, we have super-admin, or we have user. I can add other things to this, too. I can add anything to a union type. I can extract one of these two a type of itself. I can say type SuperAdmin equals super-admin, like this, and I can just put it in here. Again, it's going to give me the same behavior, so super-admin.

You can do all sorts of stuff. Object primitives can go inside here as well, so wow true, for instance, or wow boolean. You notice that when these get pretty big, they actually go into this format instead, which makes them a little bit easier to read, but is no different in terms of the behavior.

Union types are extremely powerful. We're going to use them a lot throughout the scope of total TypeScript. They allow you to do all sorts of cool stuff. Of course, I can even make this role down here into its own union type.

Really, union types are everywhere within TypeScript. They really help give your code a lot of safety and make sure that you can't pass illegal values into various places. Although, of course, they only act on the type level.

If, for instance, I wanted to somehow change this at runtime, I could do something illegal. I could say defaultUser.role equals blah, blah, blah, blah, blah. Of course, that's going to yell at me because blah, blah, blah, blah, blah is not assignable to type role. I could do something crazy like ts-ignore, for instance. That will let me pass an illegal value there.

As long as you're playing by TypeScript's rules and by not trying to override it in anyway, then union types allow you to be really sure of the types of data that are flowing through your app.