Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safely casting away nullishness in typescript

I'm looking for advice on better ways to deal with nullish values.

I'm currently learning Typescript. One of the problems I run into frequently is converting values that are potentially undefined or null to typed values that are neither undefined nor null.

e.g.

    currentTarget?: HTMLEement;

...
    let position = this.currentTarget.styles.position;
    

which of course produces an error because currentTarget is potentially undefined. Which is great. I get that.

Frequently, one knows by indirect means that a potentially null expression must not be null. My preference would be to throw an error in this case, if the expression actually is null, rather than use null coalescing operators or guarding if statements to make the error go away. For example, I could write someObject?.someProperty?.someMethod() which makes the error go away; but it ignores the fact that it is almost always an error if any of those null coalescing operators have effect. It would be better (although impossible) to write someObject.someProperty.someMethod() and have an exception thrown.

I've taken to writing guard functions to get rid of nullishness. e.g.:

model?: Model;

getModel(): Model {
    if (!this.model) throw new StateError("Model is null.");
    return this.model;
}

and have contemplated the idea of:

function nullCast<T>(value: T | null | undefined): T {
   if (!value) throw new Error("Value is null");
   return value;
}

[edit: corrected the code for nullCast (which works amazingly well.]

There's nothing terribly awful about

if (!this.model) throw new StateError("Model is null");
... carry on with non-nullish model...

And I am gradually learning to provide default values for variable, even when they are of complex type, rather than declaring objects as nullish-able.

{
  model: SomeModel.EmptyModel();
     ... instead of ...
  model?: SomeModel;

}

But in the end, it seems like a non-negligble portion of my code is dealing with nullish-able variables that I know I know to be not nullish by virtue of the fact that the function I am in would not be executing if the program was in a state where those variables could have null or undefined values.

A compelling example currently at hand: a Model class that fetches initial state from a remote server via websocket. The initial state is populated asynchronously, during which time a "loading..." screen is displayed. This in turn forces the code implementing the UI to deal with null or undefined model properties which could not possibly be null if the UI is being displayed.

While I appreciate that it takes effort to be perfectly correct, I can't help thinking I'm missing better ways to deal with this. Advice on alternatives or best practices appreciated.

like image 672
Robin Davies Avatar asked Feb 02 '26 03:02

Robin Davies


1 Answers

"For example, I could write someObject?.someProperty?.someMethod() which makes the error go away; but it ignores the fact that it is almost always an error if any of those null coalescing operators have effect."

If having a null value there is an exceptional situation, then your types should not allow that null. It sounds like your types are too permissive. If it's an error when the value is null, then it stands to reason it should be a type error when the value is null.

Dealing with nullish values is something that we all have to deal with, but it's really hard to generalize about since what to do in the null case is very dependent on how your app/code works.


In general, when I have properties that need be non-null most of the time, I declare them as such.

interface MyType {
  someObject: {
    someProperty: {
      someMethod(): void
    }
  }
}

And then only make the fields optional when required for that circumstance:

function makeMyType(values: Partial<MyType>): MyType {
  return { ...defaultMyType, ...values }
}

Sometimes it can't really be avoided easily, and in this cases I do end up doing a lot this:

if (!foo) throw new Error('really expected foo to be here!')
console.log(foo.bar)

Or in some cases just:

if (!foo) return
console.log(foo.bar)

Which is fine. If it ever really is nullish, then you get a nice error message about what went went wrong. Or, in the second case, you just bail from a function that would be invalid in the null case.


It's hard to advise more specifically without the details of your types. But in the end if:

"it seems like a non-negligble portion of my code is dealing with nullish-able variables"

then I would look at your types first to make sure you remove as much nullishness as you can.


Another option is to use a type predicate function to validate whole objects which you can then use without testing every single property everywhere:

interface Foo {
  foo: string
  bar: string
  baz: string
}

function validateFoo(maybeFoo: Partial<Foo>): maybeFoo is Foo {
  return !!maybeFoo.foo && !!maybeFoo.bar && !!maybeFoo.baz
}

const partialFoo: Partial<Foo> = { bar: 'a,b,c' }
if (validateFoo(partialFoo)) {
  partialFoo.bar.split(',') // works
}

Playground

like image 116
Alex Wayne Avatar answered Feb 03 '26 16:02

Alex Wayne



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!