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

Implementing a 12-month display using material-ui components

Just starting out with ReactJs, TypeScript, and material-ui. Looking to display something similar to this design: https://i.stack.imgur.com/zIgUH.png Wondering if it's achievable with material-ui. If not, any suggestions for alternatives? Appreciate ...

The ngFor directive in Angular should be used with the Filter pipe to ensure that

Having a Filter implemented in my Angular Project that fetches data from Firebase. The current status in the Filter is as follows: Name 1: Lea Muster Name 2: Bruno Mustermann Name 3: Lea Muster Name 4: Gabriela Musterfrau The goal is to show duplicate e ...

Angular - Using the 'name' attribute with the 'mat-select' element

Currently, I am working on an Angular form that involves the dynamic nature of the userEntitiesRoles array. To ensure smooth functionality, each mat-select tag within the ngFor loop requires a unique name attribute. In order to achieve this, I attempted to ...

Is there a way for me to showcase a particular PDF file from an S3 bucket using a custom URL that corresponds to the object's name

Currently, I have a collection of PDFs stored on S3 and am in the process of developing an app that requires me to display these PDFs based on their object names. For instance, there is a PDF named "photosynthesis 1.pdf" located in the biology/ folder, and ...

The error message "The type 'DynamicModule' from Nest.js cannot be assigned to the type 'ForwardReference' within the nest-modules/mailer" was encountered during development

Recently, I decided to enhance my Nest.js application by integrating the MailerModule. I thought of using the helpful guide provided at this link: Acting on this idea, I went ahead and performed the following steps: To start with, I executed the command ...

An error occurs when attempting to access a property that does not exist on type 'never'. Why is this considered an error rather than a warning?

I am experiencing an issue with the following code snippet: let count: number | undefined | null = 10; count = null; let result: string | undefined | null = count?.toFixed(2); console.log(`Result: ${result}`); The error message I received is as follows: ...

Generating dynamic components using React and TypeScript

Creating a multi-step form using a set of components can be tricky. One approach is to compile all the components into an array and then use the map() method to render them in sequence. const stepComponents = [ <SelectCoach/>, <SelectDate/> ...

"Exploring the incredible powers of Ionic2, Angular2, HTTP requests, and

Despite all the research I've done on observables, I still struggle to grasp how they function. The HTTP request code snippet is as follows: import { Component, OnInit, Injectable } from '@angular/core'; import { Http, Response, Headers, R ...

What is the best way to utilize the features of component A within component B when they exist as separate entities

Component A has all the necessary functionalities, and I want to use it in Component B. The code for ComponentA.ts is extensive, but it's not written in a service. How can I utilize the logic from Component A without using a service, considering both ...

Tips for uploading images, like photos, to an iOS application using Appium

I am a beginner in the world of appium automation. Currently, I am attempting to automate an iOS native app using the following stack: appium-webdriverio-javascript-jasmine. Here is some information about my environment: Appium Desktop APP version (or ...

An issue arises when using enums in TypeScript

Let's analyze this demonstration. Initially, an enum is created as follows: enum myEnum { a = 'a', b = 'b' } The next step involves creating a similar enum but with the addition of one more numeric value! This alteration is c ...

Is it possible for Typescript to automatically infer object keys based on the value of a previous argument?

Currently, my goal is to create a translation service that includes type checking for both tags and their corresponding placeholders. I have a TagList object that outlines the available tags along with a list of required placeholders for each translated st ...

Error message: "The toJSON method is not found for the item in the nested array of

Encountering an issue with NSwag in my Angular project where it throws an error when attempting to send data if the object contains a nested array of objects like this: export interface IJobAdDto { mainJobAd: JobAddDetailsDto; differentLanguageJobA ...

Determine the date and time based on the number of days passed

Hey there! I have a dataset structured like this: let events = { "KOTH Airship": ["EVERY 19:00"], "KOTH Castle": ["EVERY 20:00"], Totem: ["EVERY 17:00", "EVERY 23:00"], Jum ...

The issue of ngModel not binding to the value of ion-select in Angular Ionic

Having an ion select outside of a form with an ngModel attribute bound to "selectedValue", I encounter an issue where my selections are not being properly populated in the selectedValue variable even though they appear in the ionChange method. The main pur ...

What is the best way to access the vue3datepicker object in order to manually close the date picker popup user interface?

Enhancement After yoduh's feedback, I made adjustments to the code below. However, vue3datepicker is still undefined. Code has been updated according to yodubs suggestion. I consulted the official vue3datepicker documentation to customize my own Act ...

Encountering an issue with Next.js, Typescript, and mongoose when attempting to use `let cached = global.mongoose

I attempted to create a cached mongoose connection for my Next.js + Typescript application, but the code I used was: let cached = global.mongoose; if (!cached) { cached = global.mongoose = { conn: null, promise: null }; } The use of global.mongoose res ...

Step-by-step guide: Mocking a fetch request in Jest using React with TypeScript

In my project, I am developing a react+ts application which allows users to search for other users using the GitHub API. The search input element in my app has the following structure : <input type="text" placeholder="Search us ...

Determine an expression based on a string in Typescript

Looking at the code snippet below, everything appears to be in order (view playground): type PathParam<T> = T extends `${string}:${infer U}` ? U : never; type Param = PathParam<"/post/:post_id">; // type Param = "post_id" ...

What could be causing Next.js to re-render the entire page unnecessarily?

As a newcomer to Next.js, I am trying to develop an app where the header/navbar remains fixed at all times. Essentially, when the user navigates to different pages, only the main content should update without refreshing the navbar. Below is the code I have ...