whale
whale

Reputation: 1

How to Restrict the Type of an Optional Value in TypeScript?

I'm trying to refine the type of an optional value in TypeScript. I want to precisely determine the type of the 'value' based on the 'text' property of the 'option' in TypeScript.

typescript

import "./styles.css";

// type SelectOption = {
//   text: 'a' | 'b';
//   value: SelectOption[text] === 'a' ? string : number
// };

type SelectOption =
  | {
      text: "a";
      value: string;
    }
  | {
      text: "b";
      value: number;
    };

type MultySelectOption =
  | {
      text: "c";
      value: boolean;
    }
  | {
      text: "d";
      value: Record<string, string>;
    };

type OptionsBy<T extends string> = T extends "select"
  ? SelectOption[]
  : T extends "multiSelect"
  ? MultySelectOption[]
  : never;

export default function App() {
  const options: OptionsBy<"select"> = [];
  const numberValue = options?.find(({ text }) => text === "b")?.value;
  //numberValue: string | number | undefined

  const sum = () => {
    return numberValue + 10;
  };

  return <div className="App"></div>;
}

I want to narrow down the type of numberValue from string | number | undefined to just number | undefined. How can I achieve this in the provided code?

codesandbox link http://codesandbox.io/p/sandbox/vigorous-ishizaka-xsn7cq?file=%2Fsrc%2FApp.tsx%3A1%2C1-40%2C1

Upvotes: 0

Views: 53

Answers (3)

Laycookie
Laycookie

Reputation: 735

You can simply use an if statement, to check if the type of your numberValue is not string.

if (typeof numberValue !== "string") {
// In here the type of numberValue will be interpreted as `number | undefined`
}

In addition, and I assume you don't want that you can just remove the part of code that is responsible for giving numberValue a type of string.

{
      text: "a";
      value: string;
}

Even trough this answers your question, I much prefer what @cj0x39e did and I would advice you to just rewrite your types. And even trough I don't know what you are writing it feels like you might want to use classes for your specific use case but this is just food for thoughts.

Upvotes: 1

Brent
Brent

Reputation: 682

You can use the options above, but also, if you are working on the code and know beyond the shadow of doubt that the options code will always return a number or undefined (not the greatest Idea, but it will work) then you can do this:

export default function App() {
  const options: OptionsBy<"select"> = [];
  let numberValue = options?.find(({ text }) => text === "b")?.value as
    | number
    | undefined;
  //numberValue: string | number | undefined

  const sum = () => {
    if (numberValue) {
      return numberValue + 10;
    } else {
      return "numberValue was undefined"
    }
  };

Upvotes: 0

cj0x39e
cj0x39e

Reputation: 171

You can easily achieve this with the following code without any doubt. In my opinion, 'generic types' is not the best choice for this scenario.


interface Option {
    text: string;
    value: string | number;
}

interface NumberOption extends Option {
    value: number;
}

interface StringOption extends Option {
    value: string;
}

Upvotes: 1

Related Questions