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

Typescript's identification of a dispute between RequireJS and NodeJS definitions

I obtained the Typescript RequireJS definition from Definitely Typed. It includes an ambient declaration of Require that clashes with the NodeJs command "require". See below for the declaration and the error message: Declaration: declare var require: Req ...

In the application I'm developing, I'm utilizing React alongside TypeScript, making use of the useContext and useReducer hooks. However, I've encountered an issue where the dispatch function is not being

Currently, I have implemented a feature where two lists are displayed as cards based on one main list of objects. The goal is to toggle the 'favorite' value for each card item when the star button is clicked. This action should move the favorited ...

Utilizing the React TypeScript useContext hook with useReducer for state management

I'm struggling to identify the type error present in this code snippet import React from 'react'; interface IMenu { items: { title: string, active: boolean, label: string }[] } type Action = | { type: 'SET_ACTIVE&a ...

Is it possible to enhance an interface by integrating the characteristics of a constant?

I am currently working on customizing a material-ui v4 Theme. Within our separate @our-project/ui package, we have the following: export declare const themeOptions: { palette: { // some colors missing from Palette } status: string; // other pro ...

display a dual-column list using ngFor in Angular

I encountered a situation where I needed to display data from an object response in 2 columns. The catch is that the number of items in the data can vary, with odd and even numbers. To illustrate, let's assume I have 5 data items to display using 2 co ...

Navigating through the exported components of a module without explicit type declarations

So I'm in the process of developing a module with sub-modules for Angular. Here's the notation I'm using: module App.services { export class SomeService { } } When initializing all services, I use the following code snippet: function ...

The NGINX reverse proxy fails to forward requests to an Express application

I am currently in the process of setting up a dedicated API backend for a website that operates on /mypath, but I am encountering issues with NGINX not properly proxying requests. Below is the nginx configuration located within the sites-enabled directory ...

Determine the sum of exported identifiers based on ESLint rules

Currently, I am facing a requirement in my JavaScript/TypeScript monorepo to ensure that each library maintains a minimal amount of exported identifiers. Is there any existing eslint rule or package available that can keep track of the total number of exp ...

Tips for efficiently handling state across various forms in separate components using only one save button within a React-Redux application

I am currently developing an application that combines a .NET Core backend with a React frontend, using React Hook Form for managing forms. Unlike typical single-page applications, my frontend is not built in such a way. On a specific page of the applicat ...

Implementing the 'keepAlive' feature in Axios with NodeJS

I've scoured through numerous sources of documentation, Stack Overflow threads, and various blog posts but I'm still unable to make the 'keepAlive' functionality work. What could I be overlooking? Here's my server setup: import ex ...

Discovering and Implementing Background Color Adjustments for Recently Modified or Added Rows with Errors or Blank Cells in a dx-data-grid

What is the process for detecting and applying background color changes to the most recently added or edited row in a dx-data-grid for Angular TS if incorrect data is entered in a cell or if there are empty cells? <dx-data-grid [dataSource]="data ...

What is the best way to overload a function based on the shape of the argument object in

If I am looking to verify the length of a string in two different ways (either fixed or within a specific range), I can do so using the following examples: /* Fixed check */ check('abc', {length: 1}); // false check('abc', {length: 3}) ...

`Typescript does not adhere to the specified type when used inside a for loop with a local

This code snippet declares a variable venuelist of type Venue array and initializes it as an empty array. The type Venue has a property named neighborhood. There is a for loop that iterates through the venuelist array and checks if the neighborhoods matc ...

As I attempt to log in, the GitHub API is sending back a message stating that authentication

const fetchUser = async () =>{ let usernameValue : any = (document.getElementById('username') as HTMLInputElement).value; let passwordValue : any = (document.getElementById('password') as HTMLInputElement).value; const ...

Error message 'Module not found' occurring while utilizing dynamic import

After removing CRA and setting up webpack/babel manually, I've encountered issues with dynamic imports. The following code snippet works: import("./" + "CloudIcon" + ".svg") .then(file => { console.log(file); }) However, this snip ...

Coding with Angular 4 in JavaScript

Currently, I am utilizing Angular 4 within Visual Studio Code and am looking to incorporate a JavaScript function into my code. Within the home.component.html file: <html> <body> <button onclick="myFunction()">Click me</button> ...

Ways to compel string type or disregard type?

When using the following code snippet: <Image src={user?.profilePictureUrl} alt={user?.name} /> An error is encountered: Type 'string | null | undefined' is not assignable to type 'string | StaticImport'. Type 'undefined ...

The command "ng test" threw an error due to an unexpected token 'const' being encountered

Any assistance with this matter would be greatly appreciated. I am in the process of constructing an Angular 5 project using angular/cli. The majority of the project has been built without any issues regarding the build or serve commands. However, when a ...

Unable to trigger an event from an asynchronous method in TypeScript

In my scenario, I have a child component that needs to emit an event. However, I require the parent handler method to be async. The issue arises when the parent does not receive the emitted object in this particular configuration: Parent Component <co ...

What's the best way to maintain the return type of a function as Promise<MyObject[]> when using forEach method?

I am currently working with a function called search, which at the moment is set up to return a type of Promise<MyObject[]>: export function search(args: SearchInput) { return SomeInterface.performSearch(args) .then(xmlRequest =&g ...