In the past, it was impossible to access all properties simultaneously from a TypeScript union

Recently, I purchased an online course that I had put off watching until now. In the course, it specifically mentioned that certain code in TypeScript was not allowed:

type Name = { name: string }
type Age = { age: number }

type UnionBoth = Name | Age

const test = (union: UnionBoth): void => {
    if ('name' in union && 'age' in union) {
        union.name
        union.age
    }
}

However, when I tested this code on version 5.4.3, it surprisingly worked without any errors. Intrigued by this, I decided to run the same code on a previous version and encountered an error message at version 4.8.4:

Property 'name' does not exist on type 'never'.
Property 'age' does not exist on type 'never'.

This led me to question my understanding of unions in TypeScript. Could you provide some clarification on the correct mental model for interpreting the code above?


A closer inspection revealed that in version 5.4.3, the inferred type of union within the function is as follows:

(parameter) union: Name & Record<"age", unknown>

Why does there need to be a Record for just the property age?


This is my current mental model:

  1. There are two sets Name and Age.
  2. The union union can belong to one of three cases:
    1. It only belongs to Name.
    2. It only belongs to Age.
    3. It belongs to both, i.e., Name & Age.
  3. Through type-narrowing in the if statement, TypeScript should recognize the third case, allowing access to both name and age within the block.

Answer №1

From the beginning, TypeScript has implemented narrowing with the in operator, allowing developers to narrow down object types based on property existence. With TypeScript 4.9, a new form of narrowing called unlisted property narrowing was introduced using the same in operator. Despite being inconsistent, these features aim to provide flexibility while programming in TypeScript.

The design goal of TypeScript's type system is centered around supporting idiomatic JavaScript and enhancing developer productivity rather than focusing solely on soundness. This deliberate choice allows for more agile development practices, as outlined in the TypeScript Design Goals.

While TypeScript typically treats object types as open/extendible, it also includes measures such as excess property checks and in operator narrowing to prevent errors caused by unexpected properties.


Regarding the mental model for the in operator, TypeScript applies different strategies depending on whether the checked property exists within any union member or not. This results in the elimination or addition of properties based on the context, showcasing how TypeScript deals with union types dynamically.

In practical use cases, when utilizing in for property checking in unions, developers must be aware of the implications of narrowing behavior. Refactoring union types to explicitly define key relationships can provide clearer outcomes and avoid misleading narrowing scenarios.

To explore and experiment with examples related to this topic, you can access the code provided through the Playground link.

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

How to correctly type socket events when developing a customized useSocket hook in TypeScript?

Both my socket server and socket client are set to listen for a specific range of events. Below are the definitions for these socket events: import { Server } from "socket.io"; import { Socket } from "socket.io-client"; import { Disconn ...

I'm struggling to figure out how to specify the data type for storing an array of objects in a variable using React.useState

I am currently working on extracting values from an .xlsx file using the SheetJS library. Below, I will provide the code snippets, errors encountered, and the different approaches I have attempted. Data extracted from var dataToJson: (6) [{…}, {…}, { ...

The database did not respond, causing the API to resolve without sending a response for the specified endpoint (/api/dates). This could potentially lead to requests becoming stalled in Next

I have been attempting to retrieve a list of dates from my database in a straightforward manner, but unfortunately, I am not receiving any response (even after trying to log the response data to the console). The only feedback I seem to be getting when sel ...

Error animation on client-side validation not resetting correctly

Incorporated a form validation and error display system utilizing TransitionGroup for animations. The flag issueVisible manages the visibility of the error message, while determineField() aids in identifying the field related to the error. The issue arise ...

Error TS[2339]: Property does not exist on type '() => Promise<(Document<unknown, {}, IUser> & Omit<IUser & { _id: ObjectId; }, never>) | null>'

After defining the user schema, I have encountered an issue with TypeScript. The error message Property 'comparePassword' does not exist on type '() => Promise<(Document<unknown, {}, IUser> & Omit<IUser & { _id: Object ...

What is the syntax for implementing React.forwardRef in a dynamic Anchor or Button component?

I am working on a component that can act as either a button or an anchor tag. However, I am facing challenges in implementing conditional typing for the ref. How can I resolve this issue and make the ref acceptable? type ConditionalElements = | ({ ...

How can a div be displayed next to another div when it is clicked using angular2?

Is there a way to have another page show up after clicking on a div, like shown in the image below? Before Click: https://i.sstatic.net/qSBbc.png After Click: https://i.sstatic.net/julH4.png ...

Building a PathString Tree

I encountered a similar issue like the one discussed in this post (Get a tree like structure out of path string). I attempted to implement the suggested solution but I am facing difficulties getting it to work within an Angular context. The concept involv ...

I am facing difficulty in deploying my Next.js app with Firestore

Having trouble deploying my application using npm run build, Vercel, or another service. It works fine locally without showing any errors. I am using Firebase, Next.js, and TypeScript. The issue seems to be related to getStaticProps. When requesting data, ...

A: TypeScript Error TS7006: Parameter implicitly has an 'any' type

TS7006: The parameter 'port' is implicitly assigned an 'any' type. constructor(port) { TS7006: The parameter 'message' is implicitly assigned an 'any' type. Emit(message) { I'm confused because all the other r ...

Capable of retrieving response data, however, the label remains invisible in the dropdown menu

Upon selecting a country, I expect the corresponding city from the database to be automatically displayed in the dropdown menu. While I was able to retrieve the state response (as seen in the console output), it is not appearing in the dropdown menu. Inte ...

Easily Organize Your Data with Kendo React Grid: Rearrange Columns and Preserve Your Custom Layout

Currently, I am working on implementing a Kendo React grid with the option set to reorderable={true} for allowing column reordering. However, I am facing difficulty in determining how to save the new order of columns. Which event should I use to detect whe ...

Maintaining scroll position in React Router v6

My website's homepage displays a user's feed. When a user clicks on one of the feed cards, they are taken to a different URL. However, when the user returns to the homepage, it automatically scrolls to the top. I am looking for a way to preserve ...

To work with Typescript, the 'unknown' type needs to be equipped with

I encountered an issue in vscode stating "Type 'unknown' must have a 'Symbol.iterator' method that returns an iterator." for the following line of code: bookmarks.push(...this.$auth.user?.bookmarks); let bookmarks = []; if(this.$au ...

Implementing pagination for an Angular Material table using an HTTP request to set the page

How can I update the table page index when users click on next and previous buttons in an API that fetches data based on a specified Page number? It's important to note that I have already created a shared table. @Input() columns: Column[] = []; @In ...

Collaborate on Typescript Interfaces within a Firebase development environment

I've been developing a Firebase project using Angular for the frontend, and incorporating the @angular/fire library. Within this project, I have created multiple interfaces that utilize firebase and firestore types. For example: export interface ...

Encountering an issue while attempting to import the react-autosuggest module, receiving the following error

I recently encountered an issue while trying to import the react-autosuggestion module in my TypeScript file on Ubuntu v18 OS. Initially, I executed the following command: sudo npm install react-autosuggest --save import Autosuggest from 'react- ...

Tips for transferring information between two components when a button is clicked in Angular 2

I am currently working on a code that displays a table on the main page with two buttons, "Edit" and "Delete", for each row. When the Edit button is clicked, a modal opens up. My question is, how can I pass the "employee id" of a specific employee to the ...

Is there a way to determine the most recent Typescript target compatibility for every Node version?

When using any version of Node, how can I identify the appropriate Typescript Compiler Option for target that offers the most functionality? I want to eliminate any guesswork. Specify the ECMAScript target version as: "ES3" (default), "ES5", "ES6"/"ES20 ...

Angular: Stop additional input from being processed in Child Component and disable Change Detection

Is there a way to limit the number of inputs a child input in Angular receives before stopping further changes? For example, I want the child input to accept only 3 updates from ngOnChanges and then ignore any subsequent ones. I am currently using an inpu ...