Home > front end >  Why isn't typescript inferring return type correctly based on params passed?
Why isn't typescript inferring return type correctly based on params passed?

Time:05-31

I have this function with a switch case that returns two different results based on the case.

export function getTimeAgo(
  date: Date | string,
  mode: "days" | "minutes" = "days"
) {
  if (typeof date === "string") {
    date = new Date(date);
  }
  switch (mode) {
    case "days":
      return getTimeAgoByDays(date);
    case "minutes":
      return getTimeAgoByMinutes(date);
    default:
      return { timeAgo: "", numFormat: null };
  }
}

The getTimeAgoByDays function:

function getTimeAgoByDays(date: Date) {
  let timeAgo = "";
  const curDate = new Date();
  const days = Math.round(
    (date.valueOf() - curDate.valueOf()) / (1000 * 60 * 60 * 24)
  );

  if (days === 0) {
    timeAgo = "Ends today";
  } else if (days < 0) {
    const nonNegativeDays = days * -1;
    timeAgo = `${nonNegativeDays} days behind`;
  } else {
    timeAgo = `${days} days left`;
  }
  if (days === 1) {
    timeAgo = timeAgo.replace("days", "day");
  }

  return { timeAgo, numFormat: days };
}

Then calling it to get the results:

const { timeAgo, numFormat: days } = getTimeAgo(date,"days");

In the days variable I get an error stating that it could be null. But based on the mode, days, shouldn't the result always be non-null?

CodePudding user response:

TypyScript does not do any control flow analysis to determine the return type of a function based on the input values and the code inside the function. When you hover over the definiton of getTimeAgo, you will see that the return type is just a union of all the different things the function returned.

{
    timeAgo: string;
    numFormat: number;
} | {
    timeAgo: string;
    numFormat: null;
}

So no matter what you pass to the function, the return type will always be this union where numFormat could be null.

You can change this behaviour with function overloading. Here you explicitly tell TypeScript what the return type will be based on the arguments.

export function getTimeAgo<
  T extends "days" | "minutes"
>(date: Date | string, mode: T): { 
  timeAgo: string, numFormat: T extends "days" | "minutes" ? number : null 
}

export function getTimeAgo(
  date: Date | string,
  mode: "days" | "minutes" = "days"
) {
  /* ... */
}

In the return type we can create conditions based on T and form a fitting return type accordingly.

Playground

But in the code you provided this does not seem to be necessary. You could also just remove the default clause of the switch statement, since it is not reachable.

  • Related