Error TS2322: Cannot assign type 'Foo | Bar' to type 'Foo & Bar'

I am attempting to save an item in an object using the object key as the discriminator for the type.

Refer to the edit below.

Below is a simple example:

type Foo = {
  id: 'foo'
}

type Bar = {
  id: 'bar'
}

type Container = {
  foo: Foo
  bar: Bar
};

const container: Container = {
  foo: { id: 'foo' },
  bar: { id: 'bar' },
};

function storeValue(value: Foo | Bar) {
  container[value.id] = value; // <= Error occurs here. Refer to below.
}

The error message I receive is as follows:

TS2322: Type 'Foo | Bar' is not assignable to type 'Foo & Bar'.
   Type 'Foo' is not assignable to type 'Foo & Bar'.
     Type 'Foo' is not assignable to type 'Bar'.
       Types of property 'id' are incompatible.
         Type '"foo"' is not assignable to type '"bar"'.

I have tried the following approach:

type Container = {
  [key in (Foo|Bar)['id']]: FooBar | undefined
}

Implementing this resolved the error... However, it also allows scenarios like assigning a Bar in container.foo:

function storeValue(value: Foo | Bar) {
  container.foo = value; // This assignment should not be allowed.
}

Is there a way to deduce the type from the key ?

type Container = {
  [key in (Foo|Bar)['id']]: ??? | undefined 
}

I have consulted the documentation, FAQ, attempted various solutions, reviewed multiple StackOverflow posts and GitHub issues... Unfortunately, I have yet to find a suitable solution.

Edit: Another example (still simplified but more aligned with my use case. Note that I am utilizing Twilio Video)

type DataPublication = {
  kind: 'data';
  // other props
}

type AudioPublication = {
  kind: 'audio';
  // other props
}

type VideoPublication = {
  kind: 'video';
  // other props
}

type Publication = DataPublication | AudioPublication | VideoPublication;

class Whatever {
  publications: {
    data: DataPublication | undefined
    audio: AudioPublication | undefined
    video: VideoPublication | undefined
  } = {
    data: undefined,
    audio: undefined,
    video: undefined
  }

  handlePublishedWorking(publication: Publication) {
    switch (publication.kind) {
      case 'data':
        this.publications.data = publication; // publication is narrowed to DataPublication
        break;
      case 'audio':
        this.publications.audio = publication; // publication is narrowed to AudioPublication
        break;
      case 'video':
        this.publications.video = publication; // publication is narrowed to VideoPublication
        break;
    }
  }

  handlePublishedNotWorking(publication: Publication) {
    this.publications[publication.kind] = publication;
  }
}

Answer №1

The problem arises when you rely on runtime data for discrimination.

Remember, TypeScript ceases to exist at runtime - it's converted to JavaScript then.

function storeValue(value: Foo | Bar) {
  container[value.id] = value;
}

At this point, TypeScript has limited knowledge of value.id; it could be either foo or boo.

Since the value can change, TypeScript cannot accurately predict the type.

Therefore, container[value.id] is of type

container['foo'] | container['boo']
, leading to the error you experienced.

You must explicitly specify the type for TypeScript to work correctly.

One approach is through control flow:

type Foo = {
  id: 'foo'
}

type Bar = {
  id: 'bar'
}

type Container = {
  foo: Foo
  bar: Bar
};

const container: Container = {
  foo: { id: 'foo' },
  bar: { id: 'bar' },
};

function storeValue(value: Foo | Bar) {
  if (value.id === 'foo')
    container['foo'] = value;
  else
    container['bar'] = value;
}

In the else scenario, TypeScript recognizes only bar as a possible value, allowing proper type checking.

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

What is the best way to determine if a user is currently in a voice channel using discord.js?

Is there a way for me to determine if a user is currently linked to a voice channel? I am trying to implement a command that allows me to remove a user from a voice channel, and here is how I am attempting to check: const user: any = interaction.options.ge ...

Does the message "The reference 'gridOptions' denotes a private component member in Angular" signify that I may not be adhering to recommended coding standards?

Utilizing ag-grid as a framework for grid development is my current approach. I have gone through a straightforward tutorial and here is the code I have so far: typography.component.html https://i.stack.imgur.com/XKjfY.png typography.component.ts i ...

Attempting to transfer a username String from the client to the server using React and Typescript

I am working on a project where I need to send the username of a logged-in user from the Client to the Server as a string. Currently, I am able to successfully send an image file, but now I also need to send a string along with it. What I aim to do is repl ...

Why do optional values of objects remain optional when being checked in an if statement in Typescript?

Recently at work, I encountered this code snippet and was left wondering why Typescript couldn't comprehend the logic behind it. If 'test' in the object can either be undefined or a string, shouldn't it logically infer that within an if ...

Angular - How to fix the issue of Async pipe not updating the View after AfterViewInit emits a new value

I have a straightforward component that contains a BehaviorSubject. Within my template, I utilize the async pipe to display the most recent value from the BehaviorSubject. When the value is emitted during the OnInit lifecycle hook, the view updates correc ...

The issue with ng-select arises when the placeholder option is selected, as it incorrectly sends "NULL" instead of an empty string

When searching for inventory, users have the option to refine their search using various criteria. If a user does not select any options, ng-select interprets this as "NULL," which causes an issue because the server expects an empty string in the GET reque ...

Is there a faster way to create a typescript constructor that uses named parameters?

import { Model } from "../../../lib/db/Model"; export enum EUserRole { admin, teacher, user, } export class UserModel extends Model { name: string; phoneNo: number; role: EUserRole; createdAt: Date; constructor({ name, p ...

Unable to display animation without first launching it on Rive web

I attempted to incorporate a Rive animation into my Angular web application <canvas riv="checkmark_icon" width="500" height="500"> <riv-animation name="idle" [play]="animate" (load)=&qu ...

choose a distinct value for every record in the table

My goal is to only change the admin status for the selected row, as shown in the images and code snippets below. When selecting 'in-progress' for the first row, I want it to update only that row's status without affecting the others. <td ...

An issue occurred while trying to run Ionic serve: [ng] Oops! The Angular Compiler is in need of TypeScript version greater than or equal to 4.4.2 and less than 4.5.0, but it seems that version 4

Issue with running the ionic serve command [ng] Error: The Angular Compiler requires TypeScript >=4.4.2 and <4.5.0 but 4.5.2 was found instead. Attempted to downgrade typescript using: npm install typescript@">=4.4.2 <4.5.0" --save-dev --save- ...

What are the solutions for handling undefined data within the scope of Typescript?

I am encountering an issue with my ngOnInit() method. The method fills a data list at the beginning and contains two different logic branches depending on whether there is a query param present (navigating back to the page) or it's the first opening o ...

Is there a way to access and read an Excel file stored within the assets folder using Angular?

I need assistance converting an excel file to a JSON format. My excel file is currently stored in the assets folder, and I am looking for guidance on how to access it from app.component.ts. Any help would be greatly appreciated! ...

Error: The value of the expression has been altered after it was already checked. Initial value was 'undefined'. An exception has occurred

Although this question has been asked multiple times, I have read similar issues and still struggle to organize my code to resolve this particular exception. Within my component, there is a property that dynamically changes based on a condition: public e ...

Leverage the generic parameter type inferred from one function to dynamically type other functions

I am in the process of developing an API for displaying a schema graph. Here is a simplified version of what it entails: interface Node { name: string; } type NodeNames<T extends Node[]> = T[number]["name"]; // Union of all node names as strings ...

The Axios and TypeScript promise rejection error is displaying an unknown type- cannot identify

Currently, I am encountering an issue where I am unable to utilize a returned error from a promise rejection due to its lack of typability with Typescript. For instance, in the scenario where a signup request fails because the username is already taken, I ...

I am facing an issue with updating the mat-table after pushing values to a

I have a uniqueFormGroup with UniqueFormArray and a special-table that displays the array. When I add new uniqueFormGroup to UniqueFormArray, the special-table doesn't add new row. I was attempting to implement trackBy, but I am unsure of where (and ...

Mastering the utilization of custom input events in PrimeNG with Angular

I am currently working on an Angular 16 project. Within this project, I have implemented a number input field that is being validated using formControls. To make my work more efficient, especially since this input is used frequently, I decided to encapsula ...

Modify associated dropdown menus

I am trying to create an edit form that includes dependent select fields (such as country, state, city). The issue I am facing is that the edit only works when I reselect the first option (car brand) because I am using the event (change) with $event. How c ...

What is the best way to retrieve Express app configuration asynchronously?

I am utilizing an Express app developed with the Serverless Framework, which will be hosted via AWS API Gateway and AWS Lambda. The authentication process is handled by Okta, and I am considering storing the necessary secrets in SSM. Currently, I have to f ...

What is the correct way to add type annotations to an Axios request?

I have meticulously added type annotations to all endpoints in my API using the openapi-typescript package. Now, I am looking to apply these annotations to my Axios requests as well. Here is a snippet of code from a Vue.js project I have been developing: ...