Use assertion functions inside classes

Assertion functions are pretty useful when they're applied to classes, and there's some cool stuff you can do. Here we've got an SDK

export class SDK {
  constructor(public loggedInUserId?: string) {}

  createPost(title: string) {
    this.assertUserIsLoggedIn()

    createPost(this.loggedInUserId, title)
  }

  assertUserIsLoggedIn() {
    if (this.loggedInUserId) {
      throw new Error("User is not logged in")
    }
  }
}

It's kind of like if you imagine the back ends at a center point where you invoke class and you've got all your methods and stuff.

And here we've got a loggedInUserId and this can either be string or it can be undefined. When it's undefined it means the user isn't logged in and so there might be still anonymous things they can do.

But inside createPost we want to assert that the user is logged in and there we're going to use. the user ID. But the issue here is that we've got string or undefined in for the first argument of the call to createPost which is not good because actually we need string and really we've asserted that the logged in user ID exists.

So we should just be able to get this going. But we haven't told TypeScript that this.loggedInUserId is defined. If we were to move the if statement in our assertion to be inside createPost here then it would work.

export class SDK {
  constructor(public loggedInUserId?: string) {}

  createPost(title: string) {
    this.assertUserIsLoggedIn()

    if (this.loggedInUserId) {
      throw new Error("User is not logged in")
    }

    createPost(this.loggedInUserId, title)
  }

  assertUserIsLoggedIn() {}
}

But how do we get this. kind of inference working? Well we can return asserts this is this &. Which means we are saying assertUserIsLoggedIn is an assertion function now and we're saying that this is this and something else and we can add loggedInUserId: string

assertUserIsLoggedIn(): asserts this is this & {
  loggedInUserId: string
} {
  if (this.loggedInUserId) {
    throw new Error("User is not logged in")
  }
}

What this does is it basically just adds an intersection to the class. and matches them both together and loggedInUserId: string wins. So we get the string inside the SDK and now this.loggedInUserId is typed string.

We could probably do even something more in here. We could say wow which is a Boolean. And then use this.wow in createPost

export class SDK {
  constructor(public loggedInUserId?: string) {}

  createPost(title: string) {
    this.assertUserIsLoggedIn()

    createPost(this.loggedInUserId, title)
    this.wow
  }

  assertUserIsLoggedIn(): asserts this is this & {
    loggedInUserId: string
    wow: boolean
  } {
    if (this.loggedInUserId) {
      throw new Error("User is not logged in")
    }
  }
}

So assertion functions are pretty useful. We do need to make sure that you check everything there. Like this.wow Boolean is technically unsafe.

But, it does give you a lot of flexibility and it means you can use it in a lot more of a reusable way.

Transcript

0:00 Assertion functions are pretty useful when they're applied to classes, and there's some cool stuff you can do. Here, we've got an SDK, which is like, if you imagine, the back end centerpoint, where you invoke this, and you've got your methods and stuff.

0:14 Here, we've got a logged-in user ID, and this can either be string, or it can be undefined. When it's undefined, it means the user isn't logged in, and so there might be still anonymous things they can do. Inside createPost, we want to assert that the user is logged in.

0:28 There, we're going to use the user ID. The issue here is that we've got string or undefined in this position, which is not good, because actually, we need string. Really, we've asserted that the logged-in user ID exists, so we should just be able to get this going, but we haven't told TypeScript that this is defined.

0:48 If we were move this stuff inside here, then it would work, but how do we get this inference working? Well, we can return assert this is this, and -- so we're saying this is an assertion function now, and we're saying that this is this and something else, and -- we're going to add logged-in user ID, string.

1:13 What this does is it basically just adds an intersection into the class and matches them both together, and this one wins. We get the string inside the SDK, and now this is typed a string. We could probably do even something more in here. We could say wow, which is a pretty Boolean, and I'm pretty sure we'll be able to go this.wow, which is a Boolean.

1:32 Assertion functions are pretty useful. You do need to make sure that you check everything there, like this wow Boolean is technically unsafe, but it does give you a lot of flexibility, and it means you can use it a lot more way of a reusable way.

You can do some really, really neat stuff with assertion functions inside classes.

Here, we assert that the user is logged in and get proper inference on the user's logged in user id.

Discuss on Twitter

More Tips

Play Type Predicates

Type Predicates

1 min

Play TypeScript 5.1 Beta is OUT!

TypeScript 5.1 Beta is OUT!

2 mins

Play How to Name your Types

How to Name your Types

4 mins

Play Don't use return types, unless...

Don't use return types, unless...

4 mins

Play TypeScript 5.0 Beta Deep Dive

TypeScript 5.0 Beta Deep Dive

6 mins

Play Conform a Derived Type Without Losing Its Literal Values

Conform a Derived Type Without Losing Its Literal Values

1 min

Play Avoid unexpected behavior of React’s useState

Avoid unexpected behavior of React’s useState

1 min

Play Understand assignability in TypeScript

Understand assignability in TypeScript

2 mins

Play Compare function overloads and generics

Compare function overloads and generics

1 min

Play Use infer in combination with string literals to manipulate keys of objects

Use infer in combination with string literals to manipulate keys of objects

1 min

Play Access deeper parts of objects and arrays

Access deeper parts of objects and arrays

1 min

Play Ensure that all call sites must be given value

Ensure that all call sites must be given value

1 min

Play Understand how TypeScript infers literal types

Understand how TypeScript infers literal types

1 min

Play Get a TypeScript package ready for release to NPM in under 2 minutes

Get a TypeScript package ready for release to NPM in under 2 minutes

1 min

Play Assign local variables to default generic slots to dry up your code and improve performance

Assign local variables to default generic slots to dry up your code and improve performance

2 mins

Play Know when to use generics

Know when to use generics

2 mins

Play Map over a union type

Map over a union type

1 min

Play Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

1 min

Play Use generics to dynamically specify the number, and type, of arguments to functions

Use generics to dynamically specify the number, and type, of arguments to functions

1 min

Play Use 'declare global' to allow types to cross module boundaries

Use 'declare global' to allow types to cross module boundaries

2 mins

Play Turn a module into a type

Turn a module into a type

2 mins

Play Create autocomplete helper which allows for arbitrary values

Create autocomplete helper which allows for arbitrary values

2 mins

Play Use deep partials to help with mocking an entity

Use deep partials to help with mocking an entity

1 min

Play Throw detailed error messages for type checks

Throw detailed error messages for type checks

1 min

Play Create a 'key remover' function which can process any generic object

Create a 'key remover' function which can process any generic object

1 min

Play Use generics in React to make dynamic and flexible components

Use generics in React to make dynamic and flexible components

1 min

Play Create your own 'objectKeys' function using generics and the 'keyof' operator

Create your own 'objectKeys' function using generics and the 'keyof' operator

1 min

Play Write your own 'PropsFrom' helper to extract props from any React component

Write your own 'PropsFrom' helper to extract props from any React component

1 min

Play Use 'extends' keyword to narrow the value of a generic

Use 'extends' keyword to narrow the value of a generic

1 min

Play Use function overloads and generics to type a compose function

Use function overloads and generics to type a compose function

2 mins

Play Decode URL search params at the type level with ts-toolbelt

Decode URL search params at the type level with ts-toolbelt

2 mins

Play Use 'in' operator to transform a union to another union

Use 'in' operator to transform a union to another union

2 mins

Play Derive a union type from an object

Derive a union type from an object

2 mins