Is it possible to store a TypeScript type predicate in a variable?

Let's consider a type predicate similar to the one shown in the TypeScript documentation:

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

Now, imagine we have some code structured like this:

function inRightPlace(pet: Fish | Bird) {
  return (
    (isFish(pet) && pet.inWater()) ||
    (!isFish(pet) && pet.inSky())
  );

We want to optimize this by avoiding calling isFish function twice. Our attempt looks like this:

function inRightPlace(pet: Fish | Bird) {
  const isAFish = isFish(pet);
  return (
    (isAFish && pet.inWater()) ||
    (!isAFish && pet.inSky())
  );

However, the TypeScript compiler raises an issue because the check on isAFish doesn't act as a recognized type test for pet. Various attempts have been made to assign a type to isAFish, but it seems that type predicates cannot be used in this way.

It appears there may not be a way to avoid the duplicate call to the type guard function in this scenario. Further research online has yielded limited information, so confirmation from experts would be greatly appreciated.

Answer №1

TS4.4 UPDATE:

With TypeScript 4.4, you'll be able to save the results of type guards to a const, thanks to the implementation found in microsoft/TypeScript#44730. This means your code example will now function seamlessly:

function inRightPlace(pet: Fish | Bird) {
  const isAFish = isFish(pet);
  return (
    (isAFish && pet.inWater()) ||
    (!isAFish && pet.inSky()) 
  ); // all good
}

Link to Playground for code


ANSWER FOR TS4.3 AND BELOW:

Unfortunately, assigning the result of a user-defined type guard to a variable and using it isn't possible with versions prior to 4.4. You can show support for this feature by checking out issues microsoft/TypeScript#24865 or microsoft/TypeScript#12184. If this is important to you, consider giving them a thumbs up.

In your specific case, using a ternary operator as a replacement for

(x && y) || (!x && z)
with x ? y : z might be a viable solution (or potentially x ? (y || false) : z if the distinction between y || false and y matters to you). This approach evaluates the user-defined type guard once and leverages the compiler's ability to narrow control flow:

function inRightPlace(pet: Fish | Bird) {
    return isFish(pet) ? (pet.inWater() || false) : pet.inSky(); // all good
}

Hopefully, this advice proves helpful. Best of luck!

Code Link here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The ultimate guide to leveraging the power of Vitejs React TS template

When I try to specify absolute paths in the 'vite.config.ts' file, Vite seems to be unable to read the path properly and throws an error in the console. // vite.config.ts // Libraries import { resolve } from 'path' import { defineCo ...

What is the equivalent of specifying globalDevDependencies for npm @types packages in typings?

I am looking to update a project using tsc@2 and remove typings from my toolchain. Updating common dependencies is easy as they are listed in my typings.json file: "dependencies": { "bluebird": "registry:npm/bluebird#3.3.4+20160515010139", "lodash": ...

Using TypeScript, you can utilize RxJS to generate a fresh Observable named "Array" from a static array

I've successfully created an observable from an array, but the issue is that its type shows as Observable<number> instead of Observable<number[]> getUsers(ids: string[]): Observable<number[]> { const arraySource = Observable.from ...

Jest unit tests are failing due to an error stating that VUE_APP_CONFIG is not defined

export interface VueAppSettings { BASE_URL: string; BASE_URL_V2: string; } declare const VUE_APP_SETTINGS: VueAppSettings; export const APP_SETTINGS = { ...VUE_APP_SETTINGS } as const; I am encountering a reference error in the code snippet abov ...

"Extra loader required to manage output from these loaders." error encountered in React and Typescript

After successfully writing package 1 in Typescript and running mocha tests, I confidently pushed the code to a git provider. I then proceeded to pull the code via npm into package 2. However, when attempting to run React with Typescript on package 2, I enc ...

You cannot use ca.select(....).from function after the code has been minified

My Angular application utilizes squel.js and functions correctly in development mode. However, upon building the app for production and attempting to use it, I encounter the following error message: ca.select(...).from is not a function This error ref ...

What is the definition of a type that has the potential to encompass any subtree within an object through recursive processes?

Consider the data structure below: const data = { animilia: { chordata: { mammalia: { carnivora: { canidae: { canis: 'lupus', vulpes: 'vulpe' } } } } }, ...

angular 2 updating material table

Issue at Hand: I am facing a problem with the interaction between a dropdown menu and a table on my website. Imagine the dropdown menu contains a list of cities, and below it is a table displaying information about those cities. I want to ensure that whe ...

I'm experiencing a strange issue where my component's values remain unchanged even after re-rendering the component with new values. I wonder if this could be a result of Next.js caching

Perhaps the title didn't fully capture what I'm trying to explain, so here's a breakdown. I'm in the process of developing a habit tracker. This tracker enables users to create their own habits which are stored in a habits mongodb tabl ...

The new update of React Native version 0.64.0 is facing issues with updating the bundle in iOS devices

Can anyone lend a hand with React Native? I've hit a roadblock since updating to RN 0.64.0. Everything seems to be working fine, except for the hot reload feature. RN doesn't seem to recognize changes, even though the Metro bundler starts up suc ...

Is it possible to retrieve a randomized set of the top 10% results from an Angular Material data source?

Sorry for the roughness of the code. It's a work in progress. While I believe it's almost working, I'm struggling to complete it. My goal is to display 10% of the results from an HTTP POST request in a material table. Any tips would be appre ...

Tips for avoiding incorrect type assignments in TypeScript

As a new TypeScript user, I'm looking for ways to prevent incorrect types from creeping into my data structures. I was surprised that TypeScript did not restrict the assignment of this.myString = myArgument, even though the type of myArgument is unkno ...

Dealing with Typescript and React: The Frustration of Property Not Found on Interface, even though it clearly

Dealing with an API where I've linked the properties to an interface using an automated tool: export interface Root { stationId: number; results: Results; } To keep it concise, I'm skipping the additional results interfaces. I then make a fe ...

The number of keys in the related object must correspond to the length of the array in Advanced Generic Types

Can we achieve type safety across rows and columns by having one object define the structure of another? Starting Point: export interface TableColumn { name: string; type: "string" | "number" | "action"; id: string; } ...

Ways to utilize the scan operator for tallying emitted values from a null observable

I'm looking for an observable that will emit a count of how many times void values are emitted. const subject = new Subject<void>(); subject.pipe( scan((acc, curr) => acc + 1, 0) ).subscribe(count => console.log(count)); subject ...

Using the Vue.js Compositions API to handle multiple API requests with a promise when the component is mounted

I have a task that requires me to make requests to 4 different places in the onmounted function using the composition api. I want to send these requests simultaneously with promises for better performance. Can anyone guide me on how to achieve this effic ...

Typescript counterpart of a collection of key-value pairs with string keys and string values

Within the API I'm currently working with, the response utilizes a data type of List<KeyValuePair<string, string>> in C#. The structure appears as shown below: "MetaData": [ { "key": "Name", &q ...

Are you familiar with Mozilla's guide on combining strings using a delimiter in Angular2+?

I found myself in need of concatenating multiple string arguments with a specific delimiter, so after searching online, I stumbled upon a helpful guide on Mozilla's website that taught me how to achieve this using the arguments object. function myCo ...

Having trouble sending an HTTP request to my .Net Core 2.1 web project

In my current project, I am working with Angular 6 and .Net Core 2.1. The Angular 6 code is in one project, while the .Net Core 2.1 controller methods for login authentication are in another project. I have noticed that both projects are using different lo ...

Invoke cloud functions independently of waiting for a response

Attempting a clever workaround with cloud functions, but struggling to pinpoint the problem. Currently utilizing now.sh for hosting serverless functions and aiming to invoke one function from another. Let's assume there are two functions defined, fet ...