# Dealing with TypeScript’s strictness

When starting a TypeScript project, it is tempting to set the "strict" flag to false. While this make TypeScript return less errors you miss out on some useful checks (e.g. for undefined variables).

# Object is possibly 'undefined' (or 'null').

This happens when some third-party API signature says that the return value may be undefined or null. But what do you do, when you know for sure it will never be undefined or null?

  • if (snapshot.exists) {
      return snapshot.data().name
    }
    

    👆 Error: snapshot.data() is possibly 'undefined'.

  • if (snapshot.exists) {
      return snapshot.data()!.name
    }
    

    You can use the “non-null assertion operator”(opens new window) here.

    If you use snapshot.data() multiple times in the project, this would mean you’ll start seeing snapshot.data()! littered throughout the codebase.

    While using ! in exceptional situations is often the pragmatic thing to do, it is not the same as sprinkling ! everywhere.

  • if (snapshot.exists) {
      return getSnapshotData(snapshot).name
    }
    

    Alternatively, you can create a custom function that never returns undefined or null. In exceptional case, it can throw an error. As a result, you have a more descriptive error message like “Document X does not exist” instead of “Cannot read property 'name' of undefined”.

    /**
     * Like `snapshot.data()`, but throws an error if data is undefined.
     */
    export function getSnapshotData<T>(
      snapshot: firestore.DocumentSnapshot<T>
    ): T {
      const data = snapshot.data()
      if (!data) {
        throw new Error(`Document "${snapshot.ref.path}" does not exist`)
      }
    
      return data
    }
    

# Argument of type string | null is not assignable to parameter of type string.

  • snapshot.forEach((child) => {
      if (child.child('done').val() === true) {
        completedIds.push(child.key)
      } else {
        notCompletedIds.push(child.key)
      }
    })
    

    👆 Error: child.key is possibly 'undefined'.

  • snapshot.forEach((child) => {
      if (child.child('done').val() === true) {
        completedIds.push(child.key!)
      } else {
        notCompletedIds.push(child.key!)
      }
    })
    

    There are situations where it is not worth it to define a utility function. In this case, we can just slap ! into child.key.

    But then banging the same thing many times may not sound like a good idea.

  • snapshot.forEach((child) => {
      const key = child.key!
      if (child.child('done').val() === true) {
        completedIds.push(key)
      } else {
        notCompletedIds.push(key)
      }
    })
    

    It is perhaps more hygeinic to extract the thing being banged into to a variable, and reuse it.