Combining different sub types using the | symbol - Exploring the power of Union Types

I have a custom type called Entry which includes:

export type Entry = {
  number: number
  position: number
  entryItem: Banana | Orange
}

Additionally, I have defined the following types for entryItem:

Banana Type

export type Banana = {
  number: number
  bananaTitle: string
}

Orange Type

export type Orange = {
  number: number
  orangeColor: string
}

However, when I try to access a property like:

entry.entryItem.bananaTitle

I encounter the error message: "Property 'bananaTitle' does not exist on type 'Banana | Orange'

Similarly, if I attempt:

entry.entryItem.orangeColor

I receive the error: "Property 'orangeColor' does not exist on type 'Banana | Orange'

I thought that using the | operator would allow me to differentiate between accessing properties of Banana and Orange when using

entry.entryItem.UNIQUE_PROPERTY_OF_BANANA_OR_ORANGE
.

What am I misunderstanding here?

Answer №1

The compiler doesn't possess magical abilities. The variable entryItem could represent either a Banana or an Apple. At the compilation level, it is necessary to inform the compiler about the specific object being used. This can be achieved in various ways, one of which involves utilizing the in operator as demonstrated below.

function checkItemType(entry: Entry) {
  if ("appleTitle" in entry.entryItem) {
     entry.entryItem.appleTitle; // this represents an apple
  } else {
    entry.entryItem.bananaTitle; // this signifies a banana
  }
}

Answer №2

When working with code that involves creating and using an Entry, it's important to consider two perspectives. One perspective focuses on the code responsible for generating an Entry, while the other deals with the code that utilizes the Entry. This approach allows for flexibility when creating an Entry, as it can be either a banana or an apple, providing more options in the process.

However, in the case of code that uses the Entry, there is less certainty about the type being handled. It could be an Apple or a Banana, making it challenging to determine without manual intervention. By default, only properties common to both types can be accessed in such scenarios.

If one wishes to interact specifically with either apples or bananas, additional code must be written to identify the type being dealt with. One method is utilizing the in operator to check for the presence of specific properties:

const Example = (entry: Entry) => {
  if ('appleTitle' in entry.entryItem) {
    // Within this block, TypeScript confirms item as an Apple, enabling access to unique apple properties.
    console.log(entry.entryItem.appleTitle);
  } else {
    console.log(entry.entryItem.bananaTitle);
  }
}

Alternatively, transforming types into a "discriminated union" by introducing a distinguishing property common to both types can help differentiate between them:

export type Banana = {
  type: 'banana',
  number: number,
  bananaTitle: string
}

export type Apple = {
  type: 'apple',
  number: number,
  appleTitle: string
}

// implementation example:
const Example = (entry: Entry) => {
  if (entry.entryItem.type === 'apple') {
    // Within this block, TypeScript recognizes item as an Apple, facilitating exclusive access to apple properties.
    console.log(entry.entryItem.appleTitle);
  } else {
    console.log(entry.entryItem.bananaTitle);
  }
}

A third approach involves establishing a user-defined type guard

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

Can HTML variables be accessed in lines of code before they are declared in HTML?

In line 1 of my code, I am trying to access the rowData variable which is declared in the second HTML line. However, I keep getting the error message "Property 'rowData' does not exist on type 'AppComponent'" for that line. Strangely, t ...

Nested asynchronous functions in TypeScript

profile.page.ts: username: string; totalScore: number; ... loadUserData() { this.spinnerDialog.show(); this.firebaseServie.loadUserData().then(() => { this.username = this.sessionData.getUser().getUsername(); this.totalSco ...

Error: The variable __WEBPACK_EXTERNAL_MODULE_XX__ has not been defined

A unique npm package called our-map has been developed utilizing TypeScript, webpack, and the ArcGIS JS API to encapsulate an esri map within a React component. The functionality of the module has been verified through testing on a dedicated page within th ...

Tips for passing certain optional parameters while excluding others without resorting to undefined or empty values like ""

Is there a way to invoke the function while omitting certain optional parameters without resorting to undefined and empty strings? import { MouseEvent } from "react"; import { DialogType } from "editor-constants"; export interface Dial ...

Getting the Correct Nested Type in TypeScript Conditional Types for Iterables

In my quest to create a type called GoodNestedIterableType, I aim to transform something from Iterable<Iterable<A>> to just A. To illustrate, let's consider the following code snippet: const arr = [ [1, 2, 3], [4, 5, 6], ] type GoodN ...

What is the function of the Angular dependency injection mechanism?

I'm trying to grasp the inner workings of Angular/NestJs dependency injection. It's intriguing how the type of parameters gets lost when a Typescript class is constructed. For example: type Dependency1 = {}; type Dependency2 = {}; class X { ...

Building an array from scratch in Angular

Below is the URL to access the code: https://stackblitz.com/edit/ng-zorro-antd-start-xz4c93 Inquiring about creating a new array. For example, upon clicking the submit button, the desired output should resemble the following structure: "tasks": [ { ...

What is the process for modifying href generation in UI-Router for Angular 2?

I am currently working on implementing subdomain support for UI-Router. Consider the following routes with a custom attribute 'domain': { name: 'mainhome', url: '/', domain: 'example.com', component: 'MainSiteC ...

Encountering the issue of receiving undefined values for both error and url during the utilization

I'm currently working on a file called createCheckoutSession.ts: import { db } from '../firebase/firebaseInit'; import { collection, addDoc, onSnapshot } from 'firebase/firestore'; import { User } from 'firebase/auth'; e ...

React Material-UI is notorious for its sluggish performance

I recently started using React Material-ui for the first time. Whenever I run yarn start in my react app, it takes quite a while (approximately 25 seconds) on my setup with an i5 8400 + 16 GB RAM. Initially, I suspected that the delay might be caused by e ...

What is the method for retrieving the name of an object's property within an Angular template

I am trying to display the name of a nested object's property using Angular interpolation <ng-container ngFor="let item of saleDetailsAggegater.productMap | keyvalue"> <tr *ngFor="let qtyMap of item.value | keyvalue"&g ...

The challenges of type verification in Redux reducer

I'm currently facing two specific challenges with Typescript and the Redux reducer. Reducer: const defaultState = { selectedLocation: { id: 0, name: 'No Location' }, allLocations: [{ id: 0, name: 'No Location' }], sele ...

Exploring the generalization of class member initialization in TypeScript

I am looking to make some modifications to the Blog constructor provided in the "Minimal working example" linked below. The objective is to refactor it using pseudo-code by leveraging a generic ModelHelper class to initialize the members of the "Blog" clas ...

Http' does not have the 'update' property

I recently implemented Angular 2 Release and utilized 'Http' from '@angular/http' for my project. However, I encountered an error when I invoked the method 'update', which resulted in the following message: "Evidently, th ...

Error TS2345: The function with arguments of type '(req: any, res: any, ctx: any) => any' cannot be assigned to the parameter of type 'HttpResponseResolver<PathParams<string>'

Encountered an issue in a React TypeScript test case involving mock data. The error message received was: TS2345: Argument of type '(req: any, res: any, ctx: any) => any' is not assignable to parameter of type 'HttpResponseResolver<P ...

Angular 2 Issue: Error Message "Cannot bind to 'ngModel'" arises after FormsModule is added to app.module

I've been struggling with the data binding aspect of this tutorial for over a day now. Here's the link to the tutorial: https://angular.io/docs/ts/latest/tutorial/toh-pt1.html The error I keep encountering is: Unhandled Promise rejection: Tem ...

Combine two elements in an array

I am faced with a challenge in binding values from an Array. My goal is to display two values in a row, then the next two values in the following row, and so on. Unfortunately, I have been unable to achieve this using *ngFor. Any assistance would be greatl ...

What made the "in" operator not the best choice in this situation?

When I set out to create a type that represents the values of a given object type, I initially came up with this: type Book = { name:string, year:number, author:string } // expected result "string" | "number" type ValueOf<T ex ...

Using Typescript to Encapsulate the Assertion that Foo Belongs to a Specific Type

For the purpose of this demonstration, let's define two dummy classes and include some example code: class X { constructor() {} } class Y extends X { value: number; constructor(value: number) { super(); this.value = valu ...

Is there a way to verify if a user taps outside a component in react-native?

I have implemented a custom select feature, but I am facing an issue with closing it when clicking outside the select or options. The "button" is essentially a TouchableOpacity, and upon clicking on it, the list of options appears. Currently, I can only cl ...