JavaScript declares that the intersection property is missing

Initially, I have a variable of type IPerson[], but after undergoing a couple of mapping iterations, it should have an added _id property, transforming it into

Array<IPerson & IWithId>
. However, upon trying to access the _id property in the fourth-to-last line, TypeScript throws an error even though the property is present, and logging works correctly by displaying the properties fname, lname, and _id.

I tried re-casting it like this:

mapped = collection.map(mapperB) as Array<IPerson & IWithId>

Unfortunately, it didn't work, and it seems overly verbose to have to manually specify the type when it should ideally be inferred based on the return type of the mapperB function.

let _id = 0;
interface IPerson { 
    fname: string;
    lname: string;
}

interface IWithId { 
    _id: number;
}

function getNumber() { 
    return _id++
}

async function getData(json: string): Promise<IPerson[]> { 
    return JSON.parse(json)
}

function mapperA(entry: IPerson): IPerson { 
    return {
        ...entry,
        lname: entry.lname.toUpperCase()
    }
}
function mapperB(entry: IPerson): IPerson & IWithId { 
    const _id = getNumber();
    return {
        ...entry,
        _id
    } 
}
async function main() {
    const json = `[{"fname":"john","lname":"doe"},{"fname":"jane","lname":"doe"}]`    
    const collection = await getData(json)
    let mapped = collection.map(mapperA)
    mapped = collection.map(mapperB)
    console.log(mapped[0]._id); // Property '_id' does not exist on type 'IPerson'.
    return mapped;
}

main().then(console.log)

I managed to make it work by using a separate variable to store the result of the second mapping function, like

const mapped2 = collection.map(mapperB)
, but I'm puzzled as to why I can't simply use the original variable?

Why doesn't TypeScript automatically infer the value of mapped from the return value of mapperB? Is there a way to achieve this?

TypeScript Playground

Answer №1

The type of mapped in TypeScript is inferred from its initial assignment, making it an IPerson[]:

TypeScript utilizes type inference in various scenarios to provide type information without explicit annotations. For instance, in the code snippet

> let x = 3;

The variable x is automatically inferred to be of type number. This type of inference occurs during variable initialization, setting default parameter values, and determining function return types.

Referenced from the "Type inference" chapter of the TypeScript handbook (linking to its upcoming 2.0 beta version) - highly recommended for further reading.

The subsequent assignment does not alter the original definition, as objects can possess additional properties. However, attempting to access _id would result in an error since TypeScript cannot guarantee that the array entries also contain _id properties based on the initial type inference.

Note: Explicitly casting with

mapped = collection.map(mapperB) as Array<IPerson & IWithId>
does not provide additional information to TypeScript, yielding the same outcome.


To enhance type clarity, I personally suggest assigning transformed values to new variables (like your suggestion with

const mapped2 = collection.map(mapperB)
). Opt for expressive variable names, even if they become slightly lengthy, to improve readability:

const filteredList = list.filter(...);
const filteredListWithIds = filteredList.map(...)

Though not directly relevant, there seems to be an issue: Array.prototype.map() returns a new array. The value of mapped from

let mapped = collection.map(mapperA)
is immediately overwritten by mapped = collection.map(mapperB) in the subsequent line. Could this be a mistake in replicating your real code within the playground example?

Answer №2

A key concept to remember in typescript is that once a variable is assigned, its type cannot be changed.

In the previous example, we demonstrated using different variables to achieve your desired outcome. However, if you prefer to use just one variable, you can chain the mappers together one after the other.

Typescript facilitates the chaining of function calls in a seamless way, allowing you to condense your code. Instead of two separate lines, you can accomplish the same result with a single line of code:

let mapped = collection.map(mapperA).map(mapperB)

We hope this solution proves helpful to you in resolving your error.

Answer №3

Issue arises in the code below:

let mapped = collection.map(mapperA) // declaring mapped with type IPerson[]
mapped = collection.map(mapperB) // attempting to change type of mapped, which is not allowed
console.log(mapped[0]._id); // accessing a property not in IPerson

Solution options include chaining the mappers or combining them into one:

function mapper(entry: IPerson): IPerson & IWithId {
    const _id = getNumber();

    return {
        ...entry,
        _id,
        lname: entry.lname.toUpperCase()
    }
}

// later in your main function
let mapped = collection.map(mapper); // now mapped will be of type (IPerson & IWithId)[]
console.log(mapped[0]._id); // able to access IWithId properties

Hopefully this resolves the issue.

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

Encountering a problem with NodeJS and ExpressJS Router.use()

Encountering an Error: // Necessary module imports // Setting up view engine app.use(favicon(path.join(__dirname, 'public/images', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.ur ...

Tips for employing the slice approach in a data-table utilizing Vue

I have a unique situation in my Vue app where I'm utilizing v-data table. The current display of data in the table works fine, but I now want to enhance it by incorporating the slice method. Here is the current data displayed in the table: https://i ...

"Using jQuery to enable ajax autocomplete feature with the ability to populate the same

I am encountering a problem with jQuery autocomplete. It works perfectly fine with one textbox, but when I create multiple textboxes using jQuery with the same ID, it only works for the first textbox and not the others. My question is how can I create mult ...

Enhance CKEditor with Linked Select Boxes Plugin

I have ventured into writing a CKEditor Plugin and have grasped the basic concepts. For instance: CKEDITOR.dialog.add( 'addDocumentGroupDialog', function ( editor ) { return { title: 'Link to a document group', min ...

Content Security Policy directive violation: Chrome extension policy error occured due to refusal to execute inline event handler

I've been working on a chrome extension to perform a simple task, but I've hit a roadblock with one line of HTML code that's causing issues with setting the correct permissions. Despite my efforts, I'm stuck on what exactly needs to be ...

connect lesson to operation

How can I dynamically bind a class in vue JS based on a function? Here is my code snippet: :class="{{this.isMobile() ? 'mobile-chip-overflow' : 'chip-overflow'}}" However, I'm encountering a console error saying "isMobile is nul ...

Issue with html2canvas image download in Firefox

I am currently using HTML2Canvas to try and download a div as an image. It works perfectly fine on Google Chrome, but I am experiencing issues with Firefox. Below is the code snippet: <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0. ...

Navigating session discrepancies: Combining various social media platforms using Next.js and NextAuth

Recently, I ran into a problem where, upon logging in with Google, I found myself needing access tokens for Twitter and LinkedIn to send out API requests. The issue came about when NextAuth modified my session data to be from either Twitter or LinkedIn ins ...

Choosing multiple lists in Angular 2 can be achieved through a simple process

I am looking to create a functionality where, upon clicking on multiple lists, the color changes from grey to pink. Clicking again will revert the color back to grey. How can I achieve this using class binding? Below is the code snippet I have tried with ...

The sorting icon in jQuery Data Table's search option is not functioning

I am having an issue with jQuery DataTables. When using jQuery DataTables, it provides a default search option. However, the problem arises when I search for a particular record and if the content does not match or if I find a single record, then I need to ...

Collapsing or expanding the Material UI accordion may lead to flickering on the page

import React from "react"; import { makeStyles } from "@material-ui/core/styles"; import Accordion from "@material-ui/core/Accordion"; import AccordionDetails from "@material-ui/core/AccordionDetails"; import Accordi ...

Fixing Bugs in Checkbox Functionality using useState in Reactjs when implementing Material UI

I am working on a numeric select functionality where selecting a number renders the component multiple times. Inside the component, there are checkboxes that should not all activate at once when one is selected. https://i.sstatic.net/Q5Csn.png You can vi ...

Retrieving specific object properties within an Angular 2 and Ionic 2 form

Within the @Page, I have a few select inputs. In addition to storing the value of the selected option, such as {{project.title}}, I also require the ability to return another selected object property, such as {{project.id}} or even the entire object. When ...

The process of locating a textarea by its id becomes obsolete when integrating CKEDITOR

The data table has editable cells, where clicking on a cell will trigger a bootstrap modal to display with a textarea containing the text retrieved from the database. Snippet of the table structure: <table class="table table-striped table-hover" id="t ...

Make a copy of an array and modify the original in a different way

Apologies for my poor English, I will do my best to be clear. :) I am working with a 3-dimensional array which is basically an array of 2-dimensional arrays. My task is to take one of these 2-dimensional arrays and rotate it 90° counterclockwise. Here is ...

Challenge encountered with implementing Next-Auth's authorize function in TypeScript

Encountering a type error in the authorize function while using NextAuth with CredentialsProvider export const authOptions : NextAuthOptions ={ session : { strategy: "jwt" }, providers : [ CredentialsProvider({ async authorize( c ...

It is not possible to include external JavaScript in a Vue.js web page

Trying to integrate a Google Translate widget onto my webpage has been a bit challenging. Initially, when I added it to a normal webpage, it worked smoothly using the following code: <div class="google_translate" id="google_translate_element"></d ...

What is the best way to add a listener for a modification of innerHTML within a <span>?

How can I detect changes inside a particular <span> element in order to attach a handler, but so far have been unsuccessful? Below is the HTML snippet: <span class="pad-truck-number-position"><?php echo $_SESSION['truckId']; ?> ...

Display various elements depending on the size of the screen within Next.js

My goal is to display a component differently depending on whether the screen width is less than 768p or not. If the width is under 768p, I want to show the hamburger menu. Otherwise, I want to display the full menu. This is the code snippet I am using. ...

Sharing information between different pages in NEXT.js version 14

After performing a fetch and receiving a successful response containing data as an object, I use router.push to redirect the page to another one where I want to display the fetched data. const handleSubmit = async (event: any) => { event.preventDefa ...