Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript: How to refine a type where one of it's properties is probably null

I'm trying to understand how to write a function that would refine my type and then return it.

It works for the passed object itself and within the function body but not outside. Furthermore what I find odd is that even if it works within the body of the function, it stops working on the return statement itself.

My expectation would be that TypeScript correctly returns a refined / narrowed down type, checked properties included not just the main object.

What am I doing wrong? Does TypeScript require more help, maybe some generic return type?

Example:

 
type Referrer = { id: string }

type User = {
    id: string;
    referrer: Referrer | null;
} | null;


function checkUserHasReferrer(user: User) {
  if (!user) {
      throw Error('No User');
  }

  if (!user.referrer) {
    console.log(user.referrer) // referrer correctly narrowed to `referrer: null`
    throw Error('No Referrer');
  }
  const referrer = user.referrer; // referrer correctly narrowed to `referrer: Referrer`
  console.log(user.referrer).     // same here, `referrer: Referrer`
  

  return user; // <- Here however it's back to `referrer: Referrer | null`
}


let initialUser: User = null;
const user = checkUserHasReferrer(initialUser);
console.log(user.id);           // <-- knows User is not `null`
console.log(user.referrer.id);  // However it doesn't know referrer is not `null`

TypeScript playground

like image 971
ovidb Avatar asked Oct 24 '25 02:10

ovidb


1 Answers

You need to use assertion functions

type Referrer = { id: string }

type User = {
  id: string;
  referrer: Referrer | null;
}



function notNull(user: unknown): asserts  user {
  if (!user) {
    throw Error('No User');
  }
}

declare const user: User | null


notNull(user)
notNull(user.referrer)

console.log(user.id);
console.log(user.referrer.id);  // ok

Playground

When you need to throw an error in TypeScript, usually such function is a good candidate to be assertion function


Or, you can use custom typeguard functions

type Referrer = { id: string }

type User = {
  id: string;
  referrer: Referrer | null;
}

const isValidUser = (user: User | null): user is User & { referrer: Referrer } =>
  Boolean(user && user.referrer)

declare const user: User

if (isValidUser(user)) {
  console.log(user.id);
  console.log(user.referrer.id);  // ok
}

Playground

like image 103
captain-yossarian Avatar answered Oct 26 '25 17:10

captain-yossarian