The or operator in Typescript does not function properly when used as a generic argument

My current configuration is as follows:

const foo = async <T>(a): Promise<T> => {
  return await a // call server here
}

type A = { bar: 'bar' } | { baz: 'baz' }
foo<A>({ bar: 'bar' })
  .then(response => {
    // error: property bar doesn't exist on type { baz: 'baz' }
    if (response.bar) console.log('bar') 

    // error: property baz doesn't exist on type { bar: 'bar' }
    if (response.baz) console.log('baz')
  })

Do you think this issue lies with TypeScript or my particular setup?

Answer №1

Regrettably, Typescript does not narrow types down when performing a simple truthy test on a property like `if (foo.bar) {}`. This is likely due to the dynamic nature of properties in JavaScript, such as property getters, proxies, and returning values like 0, false, or null. In other words, the expression `if (obj.prop)` carries more implications than just checking for the existence of a property.

Fortunately, JavaScript features a special keyword called `in`, which explicitly verifies the existence of a property on an object.

Therefore, by using `if ('bar' in foo)`, Typescript can confidently refine the type for `bar`.

In your specific scenario:

if ('bar' in response) console.log('bar')

will no longer trigger an error, and additionally, `response` will now include a property named bar.

if (`bar` in response) console.log(response.bar)

In a proficient editor, after typing `response` within that `console.log(`, the editor will suggest `bar` as an option.

Answer №2

To handle this scenario differently, you can utilize type assertion :

const function = async <T>(param): Promise<T> => {
    return await param // make server call here
}

type Dog = { breed: 'bulldog' }
type Cat = { breed: 'siamese' }
type Pet = Dog | Cat

function<Pet>({ breed: 'bulldog' })
    .then(result => {
        if ((result as Dog).breed) console.log('bulldog')
        if ((result as Cat).breed) console.log('siamese')
    })

Nevertheless, I advise against using this approach. For a more intricate situation like yours, consider the following alternative:

type Dog = { kind: 'dog', breed: 'bulldog' }
type Cat = { kind: 'cat', breed: 'siamese' }
type Pet = Dog | Cat

function<Pet>({ breed: 'bulldog' })
    .then(result => {
        switch (result.kind) {
            case 'dog':
                console.log(result.breed)
                break;
            case 'cat':
                console.log(result.breed)
                break;
            default:
                console.log('unknown kind')
        }
    })

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

Declare that a method alters a value with state

Here's a more streamlined version of my code. Check out my custom hook below: export const useStep = () => { const [step, setStep] = useState<Steps>("sending"); const changeStep = (newStep: Steps) => setStep(newStep); return { ste ...

Exploring the methods of connecting with data-checked and data-unchecked attributes in Angular

Utilizing a toggle switch, I am able to determine what text to display in the div by utilizing html attributes such as data-checked and data-unchecked. In addition, I have an Angular pipe that translates all texts on the website based on the selected lang ...

How can we direct the user to another tab in Angular Mat Tab using a child component?

Within my Angular page, I have implemented 4 tabs using mat-tab. Each tab contains a child component that encapsulates smaller components to cater to the specific functionality of that tab. Now, I am faced with the challenge of navigating the user from a ...

Mouse event listener includes timeout function that updates a global variable

I'm currently working on a function that gets activated by a mouse click, but there's a 10-second cooldown period using setTimeout() before it can be triggered again. However, after the timeout, my variable doesn't get set to the correct boo ...

A data type labeled as 'undefined' needs to include a method called '[Symbol.iterator]()' which will then return an iterator

I've been working on converting my reducer from JavaScript to TypeScript, but I keep encountering a strange error that I can't seem to resolve. The issue arises when I attempt to use ellipsis for array deconstruction in the reducer [...state.mess ...

Type Vue does not contain the specified property

I am encountering an issue where I am using ref to retrieve a value, but I keep receiving the error message "Property 'value' does not exist on type 'Vue'". Below is the code snippet causing the problem: confirmPasswordRules: [ ...

Top-notch approach for consolidating Typescript Declaration files into a single comprehensive file

Is there any efficient way to package declaration files together without using the module declaration approach? declare module "path/to/file" { ... } declare module "path/to/file/sub/file" { ... } and so on. I have encountere ...

Using Typescript/JSX to assign a class instance by reference

Looking to access an object's property by reference? See the code snippet below; class Point{ x:number; y:number; constructor(x,y) { this.x=x; this.y=y; } } const a = { first: new Point(8,9), second: new Point(10,12) }; let someBoo ...

Using TypeScript and controllerAs with $rootScope

I am currently developing an application using Angular 1 and Typescript. Here is the code snippet for my Login Controller: module TheHub { /** * Controller for the login page. */ export class LoginController { static $inject = [ ...

I am seeking guidance for developing my own messaging platform, similar to Discord, using Typescript

Allow me to simplify this for you. This piece of code outlines TypeScript interfaces and namespaces for a WebSocket API that is commonly used in a chat or messaging system. It seems to define the format of messages being exchanged between a client and ser ...

In TypeScript, if all the keys in an interface are optional, then not reporting an error when an unexpected field is provided

Why doesn't TypeScript report an error when an unexpected field is provided in an interface where all keys are optional? Here is the code snippet: // This is an interface which all the key is optional interface AxiosRequestConfig { url?: string; m ...

What is the best way to determine the highest value?

How can I ensure that the data is displayed based on the condition c.date <= this.selectedReport.report_date? The current code snippet if (Math.max(...this.costs.map(c => c.date))){} seems to be causing an issue where no data is being displayed. What ...

We were unable to identify any Next.js version in your project. Please ensure that the `"next"` package is installed in either the "dependencies" or "devDependencies" section

My attempt to deploy a Next app using the Vercel CLI has hit a roadblock. After running vercel build with no errors, I proceeded to deploy with vercel deploy --prebuilt, which also went smoothly. However, when trying to move the project from the preview en ...

Angular Error: Unable to access properties of null (specifically 'validators')

I am encountering an issue with my Angular code where I receive the error message "TypeError: Cannot read properties of null (reading '_rawValidators')". import { Component, OnInit } from '@angular/core'; import { Wifi } from './wi ...

Utilizing the array.map method to access the key of an object within an array of arrays in TypeScript

Can I utilize array.map in TypeScript to apply a function with the parameter being the key of an object within an array? I have an array containing objects which have keys 'min' and 'max'. I am looking to use something like someArrayFun ...

Utilizing mapped types in a proxy solution

As I was going through the TS Handbook, I stumbled upon mapped types where there's a code snippet demonstrating how to wrap an object property into a proxy. type Proxy<T> = { get(): T; set(value: T): void; } type Proxify<T> = { ...

Tips for verifying the presence of a value within an array using checkboxes

My firestore database contains a collection named world with a sub-collection called languages I have developed two functions: one to retrieve all documents from the sub-collection languages, and another function to fetch every language if the userUid val ...

What is the best way to run tests on this method using Jest?

import { format, getDaysInMonth, getMonth, getYear, isValid, parse } from "date-fns"; export class DateService { public getDaysInMonth(month?: Date) { return getDaysInMonth(month || new Date()); } What is the best way to test this func ...

Is it possible to dynamically adjust the size of the CircleProgressComponent element in ng-circle-progress?

For my current Angular 11 project, I am facing the challenge of dynamically changing the size of the ng-circle-progress library's CircleProgressComponent element. After some research, I discovered that the element's size can be adjusted by apply ...

How to efficiently store and manage a many-to-many relationship in PostgreSQL with TypeORM

I have a products entity defined as follows: @Entity('products') export class productsEntity extends BaseEntity{ @PrimaryGeneratedColumn() id: number; //..columns @ManyToMany( type => Categories, categoryEntity => cat ...