Conditional type/interface attribute typing

Below are the interfaces I am working with:

interface Movie {
  id: number;
  title: string;
}

interface Show {
  title: string;
  ids: {
    trakt: number;
    imdb: string;
    tmdb?: number;
  };
}

interface Props {
  data: Movie | Show;
  inCountdown: boolean;
  mediaType: "movie" | "tv"
}

If the media type is set to "movie", then the data object will always be of type Movie. I want my code to understand this without requiring explicit casting or using as.

I would like to avoid doing something like this, if possible:

let docId = "";
if (mediaType === "movie") {
  docId = (data as Movie).id.toString();
}

How can I structure this interface so that I can use

let mediaType = "movie"
let data = {id: 1, title: "Dune"}
let docId = "";

if (mediaType === "movie") {
  docId = data.id.toString();
}
if (mediaType === "tv") {
  docId = data.show.ids.trakt.toString();
}

without encountering errors or warnings such as

Property 'show' does not exist on type '{ id: number; title: string; }'
?

Here's a functional demo.

Answer №1

To ensure that illegal states are unrepresentable, consider creating a union type instead of a product type.

interface Movie {
  id: number;
  title: string;
}

interface Show {
  title: string;
  ids: {
    trakt: number;
    imdb: string;
    tmdb?: number;
  };
}


interface MovieProps {
  data: Movie;
  inCountdown: boolean;
  mediaType: "movie"
}

interface ShowProps {
  data: Show;
  inCountdown: boolean;
  mediaType: "tv"
}

type Props = MovieProps | ShowProps

const Component=(props:Props)=>{
  if(props.mediaType==='tv'){
    props.data // Show
  } else{
    props.data // Movie
  }
}

Playground

Answer №2

The concept of conditional types in Typescript differs from narrowing the type. In this scenario, we specifically narrow down the type that we require.

In your particular example, it is essential to verify if any required key name exists in the data object. This process leads to a narrowing down of the data object's type to the necessary type.

You can perform key checks (or property name checks) on an object using the 'in' operator. Refer to the functional example provided below.

let mediaType = "movie";
let data = {id: 1, title: "Dune"};
let docId = "";


function fn(myData: Props['data']){
  if (mediaType === "movie" && 'id' in myData) {
    docId = myData.id.toString(); // this method avoids the need for typecasting
  }
  
  if(mediaType === "tv" && 'ids' in myData){  
    docId = myData.ids.trakt.toString();
  }
}

fn(data); 

Playground

Demo:

interface Movie {
  id: number;
  title: string;
}

interface Show {
  title: string;
  ids: {
    trakt: number;
    imdb: string;
    tmdb?: number;
  };
}

interface Props {
  data: Movie | Show;
  inCountdown: boolean;
  mediaType: "movie" | "tv"
}

let mediaType = "movie";
let data = {id: 1, title: "Dune"};
let docId = "";


function fn(data1: Props['data']){
  if (mediaType === "movie" && 'id' in data1) {
    docId = data1.id.toString();
  }
  
  if(mediaType === "tv" && 'ids' in data1){  
    docId = data1.ids.trakt.toString();
  }
}

fn(data);

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

Tips for finalizing a subscriber after a for loop finishes?

When you send a GET request to , you will receive the repositories owned by the user benawad. However, GitHub limits the number of repositories returned to 30. The user benawad currently has 246 repositories as of today (14/08/2021). In order to workarou ...

What is the process for exporting all sub-module types into a custom namespace?

When we import all types from a module using a custom-namespace, it appears to work smoothly, for example: import * as MyCustomNamespace from './my-sub-module' We are also able to export all types from a module without creating a new namespace, ...

Handling JSON data with Reactive Extensions in JavaScript

Hey everyone, I'm a beginner in Angular and RxJS coming from a background in VueJS. I've been struggling to grasp the inner workings of RxJS and would really benefit from some guidance from more experienced individuals regarding my current issue. ...

During the compilation process, Angular could not locate the exported enum

In the file models.ts, I have defined the following enum: export enum REPORTTYPE { CUSTOMER, EMPLOYEE, PROJECT } After defining it, I use this enum inside another class like so: console.log(REPORTTYPE.CUSTOMER); When I save the file, the IDE automati ...

Instantiate a fresh object using the new keyword followed by the Class constructor, and if desired,

I'm looking for a class that I can easily create new instances from and optionally assign properties using the constructor. For instance: class Person { name: string; age: number; constructor(props: {name?: string, age?: number}) { this.nam ...

Why do rows in the React-bootstrap grid layout overlap when the screen is resized?

I am working on a simple layout structure with 2 rows: Row 1 consists of 2 columns Row 2 consists of 1 column The goal is to have both rows expand in width and avoid vertical scrolling of the page. However, when resizing the screen, I want the columns of ...

Is there a way to transform a date from the format 2021-11-26T23:19:00.000+11:00 into 11/26/2021 23:19?

Having trouble formatting the date in MM/DD/YYYY HH:mm:ss format within my .ts script. I attempted to use moment.js. moment(dateToBeFormatted, "'YYYY-MM-dd'T'HH:mm:ss.SSS'Z'").format("YYYY/MM/DD HH:mm") However ...

Error: Type '() => () => Promise<void>' is not compatible with type 'EffectCallback'

Here is the code that I'm currently working with: useEffect(() => { document.querySelector('body').style['background-color'] = 'rgba(173, 232, 244,0.2)'; window.$crisp.push(['do', 'ch ...

Utilizing React Testing Library for conducting unit tests on components

Can someone help me with writing unit tests for my component using react testing library, please? I seem to be stuck. What am I missing here? Here is the code for the component: const ErrorModal = (props: {message: string}) => { const { message } ...

Pair two values from the interface

I need to extract and combine two values from an Interface: export interface CurrenciesList { currency: string; country: string; } The initial mapping looks like this: this.optionValues["currency"] = value.map(i => ({ id: i.currency, name: i.curr ...

Angular: ensure the form reverts to its initial value when the modal is closed and reopened

I am facing an issue with my items section. When I click on an item, a modal window opens allowing me to edit the text inside a textarea. However, if I make changes to the text and then cancel or close the modal, upon reopening it, the previously modified ...

Merge attributes from objects within an array

I am seeking assistance with a basic task in TypeScript as a newcomer to the language. My challenge involves manipulating an array of objects like this: // Sample data let boop = [ {a: 5, b: 10}, {a: 7, c: 8}, {a: 6, b: 7, c: 9} ]; My objectiv ...

Gatsby, in combination with TSC, does not properly transpile the rest operator

I'm attempting to integrate TypeScript with Gatsby following the guidelines provided here. However, I am encountering an issue where the tsc command is failing to transpile the spread (...) operator and producing the error shown below: WAIT Compili ...

Can we verify if strings can serve as valid property names for interfaces?

Let's consider an interface presented below: interface User { id: string; name: string; age: number; } We also have a method defined as follows: function getUserValues(properties:string[]):void { Ajax.fetch("user", properties).then( ...

What is the reason behind capitalizing Angular CLI class file imports?

After creating a basic class in Angular using the CLI starter, I encountered an issue when trying to use the imported class. Instead of functioning as expected, it returned an empty object. Through troubleshooting, I discovered that simply changing the fil ...

Is the Prisma model not reachable through Prisma Client?

I'm currently attempting to retrieve a specific property of a Prisma model using Prisma Client. The model in question is related to restaurants and includes a reviews property that also corresponds with a separate Review model. schema.prisma file: // ...

Sending Component Properties to Objects in Vue using TypeScript

Trying to assign props value as an index in a Vue component object, here is my code snippet: export default defineComponent({ props:{ pollId:{type: String} }, data(){ return{ poll: polls[this.pollId] } } }) Encountering errors wh ...

Share information by including the provider within the @component declaration in Angular

I am looking to explore a new method of passing data using providers directly within the component itself. For instance, I am curious if there is a way to pass a variable from one component to another without relying on routing or other traditional methods ...

What is causing the issue with entering the code? Exploring the restrictions of the mui tag

Can someone explain why I am unable to use boolean data type in this code snippet? I am trying to determine whether the user is an admin or not, and based on that, hide or disable a button. const Authenticate = useSelector(userSelector) let checkValue ...

Issue: Transition of FCM to HTTP v1 API from Previous Legacy API

Recently, I have been working on migrating FCM from the legacy API to the HTTP V1 API. Here's a comparison of the "working code before" and after the necessary modifications: Before: const payload = { data: ...