Specific generic types do not incorporate abstract type context

Take a look at this code snippet:

type Data = {
  person: { id: number; name: string; age: number }
  item: { id: number; name: string; price: number }
  transaction: { id: number; personId: number; itemId: number; quantity: number }
}

type Action<T extends keyof Data> = {
  type: T
  payload: Data[T]
}

function fetchData<T extends keyof Data>(
  action: Action<T>,
) {
  const actionPayload = action.payload as Data[T] // <---- not functioning properly, only showing id.

  switch (action.type) {
    case "item":
      const item = action.payload as Data["item"]
      return `item is ${item.name}`
    case "transaction":
      const transaction = action.payload as Data["transaction"]
      return `transaction is ${transaction.itemId}`
    case "person":
      const person = action.payload as Data["person"]
      return `name is ${person.name}`
    default:
      return
  }
}

const personAction: Action<"person"> = {
  type: "person",
  payload: { id: 1, name: "John", age: 30 },
}

const itemAction: Action<"item"> = {
  type: "item",
  payload: { id: 201, name: "Gadget", price: 49.99 },
}

const transactionAction: Action<"transaction"> = {
  type: "transaction",
  payload: { id: 1001, personId: 2, itemId: 201, quantity: 2 },
}

console.log(fetchData(personAction))
console.log(fetchData(itemAction))
console.log(fetchData(transactionAction))

As I attempt to delete this line of code:

const item = action.payload as Data['item'];

...and create a new constant above the switch case:

const actionPayload = action.payload as Data[T]

then utilize it like so:

    case "person":
      return `name is ${actionPayload.name}`

typescript raises the following issue:

Property 'name' does not exist on type '{ id: number; name: string; age: number; } | { id: number; name: string; price: number; } | { id: number; personId: number; itemId: number; quantity: number; }'.

and upon inspecting context, only the id field is present. What causes this problem and how can it be resolved?

Answer №1

When working with TypeScript, it's important to note that generic types in the body of a generic function cannot be narrowed down. This means that using ResourceData[T] within a code block where a function argument typed as T has been narrowed to 'user' will not also narrow the type T.

As a result, ResourceData[T] still encompasses ResourceData['order'], which lacks a name property. Hence, when attempting to access that property, TypeScript raises an error.

The reason for this behavior is that TypeScript allows passing union types as arguments. For instance:

const unknownAction = Math.random() > 0.5 ? userAction : productAction;
fetchActionData(unknownAction); // <- fetchActionData<"user" | "product">

TypeScript 5.3 might introduce a feature allowing some narrowing in generic functions by defining a lower bound for generic types. Nevertheless, this wouldn't resolve the issue at hand.

One workaround is to utilize unions for argument types instead of generic types if a function's return type isn't impacted by them. Simultaneous narrowing of multiple arguments can be managed through spread syntax with a union of tuple types.

In cases where a function must return various types, overloads or type assertions could be utilized. Type assertions, while they come with their caveats, are valuable tools for conveying information to the TypeScript compiler that it might not deduce on its own.

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

Is it possible to create and manage a hierarchical menu in React (Next.js) using a generic approach?

Over the past few days, I've been working on a project involving a navigation bar built with TypeScript and React (Next.js). Up until now, I've only had a single level navigation, but now I'm looking to upgrade to a multi-level navigation me ...

The function 'appendChild' is not recognized on the type 'unknown'.ts(2339)

I'm encountering an issue while trying to integrate the Utterances component into my articles. Upon attempting to build the site, I receive the following error message: "Property 'appendChild' does not exist on type 'unknown' ...

Setting a variable based on the stage of its deployment in a DevOps environment: What you need to know

Is there a way I can easily update a variable in a React app based on the stage of an Azure DevOps release pipeline? For instance, if I have dev, QA, and production stages set up, and I want to change the client ID in the auth configuration for each envi ...

Utilize or Bring in an external JavaScript file within Ionic 2

Currently working with Ionic 2 and Typescript Angular 2 and facing an issue. I need to utilize an external JavaScript file located at . How can I import or include this in my project? ...

Alter the command from 'require' to an 'import'

Utilizing https://www.npmjs.com/package/json-bigint with native BigInt functionality has been a challenge. In the CommonJS environment, the following code is typically used: var JSONbigNative = require('json-bigint')({ useNativeBigInt: true }); ...

What is the true function of the `as` keyword within a mapped type?

I am new to typescript and I find the usage of as confusing in the following example. type foo = "a" | "b" | 1 | 2; type bar = { [k in foo as number]: any } This example passes type checking. The resulting bar type is transformed i ...

What is the best approach to have a method in the parent class identify the type based on a method in the child class using TypeScript?

I'm faced with a code snippet that looks like this. class Base{ private getData(): Data | undefined{ return undefined } public get output(): Data | undefined { return { data: this.getData() } } } class ...

What is the best way to ensure the secure signing of a transaction in my Solana decentralized application (

I am currently involved in an NFT project that recently experienced a security breach, and I am developing a dapp to rectify the situation. Our plan is to eliminate all NFTs from the compromised collection and issue a new set of NFTs using our updated auth ...

What is the best way to import a reusable component from the theme folder in a React Native project?

I'm interested in importing a Button component that can be reused from the theme folder. The path to the Button component is as follows: \app\theme\components\Button.ts Here is the code for Button.ts: import { typography } from ...

A programming element that is capable of accessing a data member, but mandates the use of a setter method for modifications

I am unsure whether I need a class or an interface, but my goal is to create an object with a member variable that can be easily accessed like a regular variable. For example: interface LineRange { begin: number; end: number; } However, I want th ...

What is the process for configuring React on one server and springboot on a separate server?

Can you help me with the setup of the following: Web Server : I need to set up a react + typescript application using npm at Backend Server : I also need to configure a Springboot backend server at I am currently using webpack to build the react applica ...

Typescript custom react hook - toggling with useToggle

I developed a custom hook to toggle boolean values: import { useState } from 'react'; export function useToggle(initialValue: boolean) { const [value, setValue] = useState<boolean>(initialValue); const toggleValue = () => setValue ...

Issue arises when trying to set object members using a callback function in Typescript

I am facing a peculiar issue that I have yet to unravel. My goal is to display a textbox component in Angular 2, where you can input a message, specify a button label, and define a callback function that will be triggered upon button click. Below is the c ...

How can I iterate through a variable in TypeScript?

initialize() { var elements = []; for (let i = 1; i <= 4; i++) { let node = { name: `Node ${i}` }; elements.push({ [`node${i}`]: node }); if (i < 4) { let edge = { source: `node${i}`, target: ...

Definition in TypeScript: specify a type or interface containing a field with an unidentified name

Looking to define an interface for a team object: export interface Team{ memberUid?: { mail: string name: string photoURL: string } startDate: Timestamp endDate: Timestamp agenda: Array<{ date: Date | Timestamp title: strin ...

Angular 5 Dilemma: Exporting UI Components without Locating Template

My current project involves developing UI Components that will be used in various web projects within the company. Our plan is to publish these UI components as an npm package on our local repository, and so far, the publishing process has been successful. ...

Electron triggers MouseLeave event on child elements

Dealing with mouse hover events can be a bit tricky, especially when working with AngularJS in an Electron-hosted app. Here's the HTML template and script I'm using: HTML: <div id="controlArea" (mouseenter) = "onControlAreaEnter()" ...

The program encountered an unexpected symbol. It was expecting a constructor, method, accessor, or property. Additionally, there is a possibility that the object is 'undefined'

Can someone please help me figure out what's wrong with this code snippet? import cassandra from "cassandra-driver"; class Cass { static _cass : cassandra.Client; this._cass = new cassandra.Client({ contactPoints: ['localhost&ap ...

Testing an HTTP error Observable with Jasmine and RxJS simulations

I encountered a similar issue, but due to commenting constraints on other questions, I had to create a new one. The problem lies in a jasmine test where a function is expected to manage an error from a service call. The service call returns an RxJS `Observ ...

Apollo GraphQL Server is unable to provide data to Angular

I've been facing a challenge for quite some time now trying to make my Angular component communicate with an Apollo GraphQL server using a simple Query (PingGQLService). I'm currently utilizing apollo-angular 4.1.0 and @apollo/client 3.0.0, and h ...