What is the correct way to assign multiple types to a single entity in TypeScript?

(code at the end)

While attempting to write section.full.link, I encountered the following error:

Property 'link' does not exist on type 'SectionSingle | SectionTitle | SectionHeaderMedia'. Property 'link' does not exist on type 'SectionSingle'.ts(2339)

I'm struggling to figure out how to resolve this error. Adding a ? after the full property or something similar did not help.

Here is the code snippet:

export type Section = {
    type: "single" | "double" | "title" | "headerMedia";
    full?: SectionSingle | SectionTitle | SectionHeaderMedia;
}


export type SectionSingle = {
    type: "text" | "media";
    content: any;
}

export type SectionTitle = {
    title: string;
}

export type SectionHeaderMedia = {
    link: string;
    alt: string;
}

var section: Section = {
    type: "headerMedia",
    full: {
        link: "/somelink",
        alt: "some alt text"
    }
}

const cond = section.type === "headerMedia" ? `${section.full.link}` : null

console.log(cond)

Answer №1

It appears that the current definition of Section is too broad, allowing for a value like

{type: "headerMedia", full: SectionSingle}
, where the properties full and type are not in agreement. Therefore, simply checking
section.type === "headerMedia"
does not guarantee that section.full contains a link property.

To enforce this constraint, you can define Section as a discriminated union type with type serving as the discriminant property. Here's one potential approach (though further requirements may apply):

export type Section =
    { type: "single", full: SectionSingle } |
    { type: "title", full: SectionTitle } |
    { type: "headerMedia", full: SectionHeaderMedia };

This defines a Section as one of these three possibilities. If type is checked and found to be "headerMedia", then the full property must correspond to a SectionHeaderMedia. With this setup, your verification will correctly pass without errors:

const cond = section.type === "headerMedia" ? `${section.full.link}` : null; // acceptable

Playground link to code

Answer №2

To ensure that a section is a header media type, you must implement the type guard isSectionHeaderMedia

function isSectionHeaderMedia(section: unknown): section is SectionHeaderMedia {
    return _.isObject(section) && 'link' in section;
}

_ refers to lodash library.

isSectionHeaderMedia(section) && section.type === "headerMedia"
?
<div key={index} className="w-full">
    <Image
        src={section.full.link}
    />
</div>
:   null

Answer №3

A comprehensive Section can be created to handle various scenarios and generate appropriate types as needed.

By defining a union type of all possible sections, you gain the flexibility to use them in different ways.

type HalfSection = {

}

type SectionSingleText = {

}

type SectionSingleMedia = {

}

type SectionTitle = {
    title: string;
}
type SectionHeaderMedia = {
    link: string;
    alt: string;
}

type SectionSingle = {
    type: "text" | "media"
    content: SectionSingleText | SectionSingleMedia
}

type SectionType = "single" | "double" | "title" | "headerMedia";

type SectionGeneric<ST extends SectionType> = {
    type: ST;
    full: ST extends "single" ? SectionSingle : ST extends "title" ? SectionTitle :  ST extends "headerMedia" ? SectionHeaderMedia : never;
    left?: HalfSection;
    right?: HalfSection;
}

type Section = SectionGeneric<"single"> | SectionGeneric<"double"> | SectionGeneric<"title"> | SectionGeneric<"headerMedia">

const a: Section = {
    type: "headerMedia",
    full: {
        link: "link",
        alt: "alt"
    }
};


const displayContent = (section: Section): void => {
    section.type === "headerMedia" && console.log(section.full.link)
}

displayContent(a)

Please note:

  1. Some types had to be declared for this setup to function properly.
  2. Alternatively, you could create a union type from the start.

Explore the code further on the playground.

For more information, check out Discriminated Unions.

Answer №4

To ensure type safety, you can utilize a type assertion

const condition = segment.kind === "bannerImage"
  ? `${(segment.full as SegmentBannerImage).url}`
  : null

For runtime validation, consider converting the SegmentBannerImage type into a class and introduce an additional check with the instanceof operator within an if statement

export class SegmentBannerImage {
  url = '';
  caption = '';
}

segment1.kind === 'bannerImage' && segment1.full instanceof SegmentBannerImage
    ? segment1.full.url
    : null;


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 resolving the ExtPay TypeError when using Typscript and Webpack Bundle

I am currently trying to install ExtPay, a payment library for Chrome Extension, from the following link: https://github.com/Glench/ExtPay. I followed the instructions up until step 3 which involved adding ExtPay to background.js. However, I encountered an ...

Bring in personalized tag to TypeScript

I am working on a TypeScript file to generate an HTML page. Within this code, I want to import the module "model-viewer" and incorporate it into my project. import * as fs from "fs"; import prettier from "prettier"; import React from "react"; import ReactD ...

Consolidate multiple sorting functions into a single function to effectively sort by column

Can someone help me simplify my sorting function for array columns? Currently, I have multiple functions like the one below for each column: sort() { if (this.sortAsc == false) { this.tab.sort((a, b) => { return a.name.localeCompare( ...

Creating robust unit tests for Node.js applications with the help of redis-mock

I am facing an issue while trying to establish a connection with redis and save the data in redis using the redis-mock library in node-typescript, resulting in my test failing. Below is the code snippet for the redis connection: let client: RedisClientTyp ...

The upcoming construction of 'pages/404' page will not permit the use of getInitialProps or getServerSideProps, however, these methods are not already implemented in my code

Despite my efforts to search for a solution, I have not found anyone facing the same issue as me. When I execute next build, an error occurs stating that I cannot use getInitalProps/getServerSideProps, even though these methods are not used in my 404.tsx f ...

Modify one specific variable within my comprehensive collection on Firebase Firestore

After clicking the button, I need to update a variable. The variable in question is "bagAmount" and it is stored in my firestore collection. Here is a link to view the Firestore Collection: Firestore Collection Currently, I am able to update one of the va ...

When multiple input fields with an iterative design are using the same onChange() function, the specific event.target.values for each input

I'm in the process of developing a dynamic select button that adjusts based on the information entered into the iterative input fields I've set up. These input fields all utilize the same onChange() function. for (let i = 0; i < selectCount; i ...

Is it possible to customize webpack 4 modules in order to enable Jasmine to spy on their properties?

I've been facing issues trying to run my Jasmine test suite with webpack 4. Ever since I upgraded webpack, I keep getting this error message in almost every test: Error: <spyOn> : getField is not declared writable or has no setter The problem ...

Implementing a new field in a Node.js model using MongoDB

In my Node.js API, I have a user model working with MongoDB and Angular as the front-end framework. I decided to add a new field named "municipalityDateChange" to my user model. After doing so, I attempted to send an HTTP request from Angular to the Node A ...

Changes made to an array in a called method using TypeScript do not appear in the calling function

The angular 6 calling-component-code I'm working with is as follows: this.appDowntimeService.getAllApplications(this.message, this.appDetails); Here's the service method being called: async getAllApplications(message: any[], appDetails: any[ ...

Using Kendo's Angular Grid to link data sources

I'm currently facing a minor issue with the Kendo Grid for Angular. My attempt to bind data after fetching is resulting in the following error: ERROR TypeError: Cannot read properties of undefined (reading 'api') This indicates that this. ...

How to compare and filter items from two arrays using TypeScript

I am looking to filter out certain elements from an array in TypeScript Original Array: [ {"Id":"3","DisplayName":"Fax"}, {"Id":"1","DisplayName":"Home"}, {"Id":&quo ...

What is the best way to create a distinct slug using TypeScript and mongoose?

After scouring through numerous modules, I couldn't find one that worked with TypeScript, including mongoose-slug-generator and mongoose-slug-plugin. ...

Tips for resolving conflicts between sequelize and angular within a lerna monorepo using typescript

Managing a monorepo with Lerna can be quite challenging, especially when working with both Node.js and Angular in the same project. In my setup, Angular is using "typescript": "~3.5.3". For Node.js to work seamlessly with Sequelize, I have the following ...

The base URL specified in the tsconfig file is causing the absolute path to malfunction

Displayed below is the layout of my folders on the left, along with a metro error in the terminal and my tsconfig.json configuration with baseUrl set to './src'. Additionally, I have included screenshots of my app.ts and MainTabs.ts for further c ...

Integrating Constant Contact API into a Next.js application

I'm trying to integrate the Constant Contact API into my Next.js application. I've looked through the documentation, but it only provides examples for PHP and Java. How can I effectively use the authentication flow and create an app on the dashbo ...

encountered an issue when testing a dynamic route in Next.js with postman

I recently created a new API route named route.ts, where I included two different routes. One route retrieves all users from the database, while the other retrieves a specific user based on their ID passed as a query parameter. However, when testing these ...

When ts-loader is used to import .json files, the declaration files are outputted into a separate

I've encountered a peculiar issue with my ts-loader. When I import a *.json file from node_modules, the declaration files are being generated in a subfolder within dist/ instead of directly in the dist/ folder as expected. Here is the structure of my ...

What is the method for opening the image gallery in a Progressive Web App (PWA)?

I am struggling to figure out how to access the image gallery from a Progressive Web App (PWA). The PWA allows me to take pictures and upload them on a desktop, but when it comes to a mobile device, I can only access the camera to take photos. I have tried ...

When using TypeScript with Jest or Mocha, an issue arises where the testing frameworks are unable to read JavaScript dependencies properly, resulting in an error message saying "unexpected token

Currently, I am conducting testing on a TypeScript file using Mocha. Within the file, there is a dependency that I access via the import statement, and the function I need to test resides within the same file as shown below: import { Foo } from 'foo- ...