Exploring the inner components of an entity without the need for external tools

I am currently enhancing TypeScript usage in a project by implementing generics. The challenge I am facing involves dealing with a complex object retrieved from the backend, which consists of a class with numerous attributes, most of which are classes themselves. In our form, we have a common change handler that currently contains a few instances of any to make it functional, but my goal is to enhance it using generics.

This code snippet provides a simplified version of the structure in my application, showcasing the same issues I encounter:

class PhoneNumber {
  prefix: number;
  exchange: number;
  line: number;
}

class Address {
  address1: string;
  address2: string;
  city: string;
}

class User {
  phone: PhoneNumber;
  address: Address;
}

const user = new User();

function setValue<A extends keyof User, T extends User[A], K extends keyof T, V extends T[K]>(
  value: V,
  fieldType: new () => T,
  fieldName: A,
  fieldAttribute: K
) {

  if (!user[fieldName]) {
    user[fieldName] = new fieldType();
  }

  const field = user[fieldName];

  // error here.
  field[fieldAttribute] = value;
}

setValue(408, PhoneNumber, 'phone', 'prefix');

I've attempted various approaches, and the closest solution I have reached exhibits the use of sane values for the generics during the function call at the end in my IDE:

function setValue<"phone", PhoneNumber, "prefix", number>()
. However, I encounter a compilation error:

TS2536: Type 'K' cannot be used to index type 'User[A]'.

Am I approaching this problem incorrectly? If I'm unable to resolve this issue, I may resort to segregating the different types into separate handlers as an alternative.

Furthermore, there's a scenario where some of the fields are arrays of objects, further complicating the situation.

Answer №1

When using the setValue function with specific argument types, there is a scenario where a subclass B can be passed as the fieldType (which extends class A), and target a fieldAttribute of B that may not exist in A:

class PhoneNumber2 extends PhoneNumber {
  extension: number;
}

// While this code complies with setValue types,
// it may fail if `user` already contains a basic PhoneNumber instance
setValue(408, PhoneNumber2, "phone", "extension");

// In the above scenario:
user["phone"]["extension"] = 408;
// ...but user["phone"] could actually be a PhoneNumber,
// not necessarily a PhoneNumber2!

To address this issue, you can modify the implementation by replacing K extends keyof T with K extends keyof User[A] to ensure the fieldAttribute targets the base class instead:

function setValue2<A extends keyof User, T extends User[A], K extends keyof User[A], V extends T[K]>(
  value: V,
  fieldType: new () => T,
  fieldName: A,
  fieldAttribute: K
) {

  if (!user[fieldName]) {
    user[fieldName] = new fieldType();
  }

  const field = user[fieldName];

  field[fieldAttribute] = value; // Now works correctly
}

setValue2(408, PhoneNumber, 'phone', 'prefix');
setValue2(408, PhoneNumber2, 'phone', 'prefix');
setValue2(408, PhoneNumber2, 'phone', 'extension'); // Produces an error due to incorrect type assignment

You can also simplify the function signature by removing the generic type T:

function setValue3<A extends keyof User, K extends keyof User[A], V extends User[A][K]>(
  value: V,
  fieldType: new () => User[A],
  fieldName: A,
  fieldAttribute: K
) {}

Playground Link

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

Substitute all properties of a specific type with a predetermined value in Typescript using recursive substitution

If we consider the given type structure: type Person = { name: string age: number experience: { length: number title: string } } Can we create a type like this: type FieldsOfPerson = { name: true age: true experience: { length: t ...

Why won't my code work with a jQuery selector?

I'm struggling to retrieve the value from a dynamically generated <div> using jQuery. It seems like the syntax I'm using doesn't recognize the div with an id tag. The HTML code is stored in a variable, and below is a snippet of code w ...

Is it possible for VSCode to automatically generate callback method scaffolding for TypeScript?

When working in VS + C#, typing += to an event automatically generates the event handler method scaffolding with the correct argument/return types. In TypeScript, is it possible for VS Code to offer similar functionality? For instance, take a look at the ...

Utilizing TypeScript with React to dynamically select which component to render

My task seemed simple at first: to render a React component based on its name/type. Here is an example of how it is used: // WidgetsContainer.ts // components have a difference in props shape! const componentsData = [ { type: 'WIDGET_1', ...

Issues arise in Angular 4 when the "Subscribe" function is repeatedly invoked within a for/switch loop

My array of strings always changes, for example: ["consumables", "spells", "spells", "consumables", "spells", "consumables", "spells", "characters", "characters", "consumables"] I iterate through this array and based on the index, I execute different .su ...

What is the best way to define types for an array of objects with interconnected properties?

I need to define a type for an object called root, which holds a nested array of objects called values. Each object in the array has properties named one (of any type) and all (an array of the same type as one). Below is my attempt at creating this type d ...

Troubleshooting native web worker issues in Angular 11 - Addressing the Element Bug

After upgrading Angular to version 11, I encountered issues with utilizing web workers for heavy data processing in my project. Previously, I used webworkify-webpack (https://www.npmjs.com/package/webworkify-webpack), but it stopped working post-migration. ...

Limiting the number of items shown in the dropdown panel of an NgSelect

Currently, I am utilizing Angular ngselect to showcase a dropdown menu with multiple options. However, due to the limited screen space, I need to restrict the number of items visible in the dropdown to about 3, allowing users to scroll through the rest. W ...

Having difficulty forming queries correctly using TypeScript, React, and GraphQL

Apologies for the potentially naive question, but I am new to working with GraphQL and React. I am attempting to create a component that contains a GraphQL query and incoming props. The props consist of a query that should be passed into the GraphQL query. ...

Adding a value to an array in TypeScript

When trying to add values to an array in my code, I encountered an error stating that "number" is not a valid type for the array. someArray: Array <{ m: number, d: Date}> = []; this.someArray.push(500,new Date(2020,1,15)); ...

Developing in TypeScript with styled-components allows for seamless integration between

New to TypeScript and seeking guidance. I currently have a component utilizing styled-components that I want to transition to TypeScript. import React from 'react' import PropTypes from 'prop-types' import styled from 'styled-comp ...

Typescript threw an error stating "Cannot access properties of an undefined object" in the React-Redux-axios

As a backend developer, I am not very familiar with frontend development. However, for my solo project, I am attempting to create some frontend functionalities including user login right after setting the password. Below is the code snippet from UserSlice. ...

Guide to populating a dropdown list using an array in TypeScript

I'm working on a project where I have a dropdown menu in my HTML file and an array of objects in my TypeScript file that I am fetching from an API. What is the best approach for populating the dropdown with the data from the array? ...

Understanding how to efficiently map through FontAwesome icons using React TypeScript and effectively showcase them on the frontend

I am in the process of developing a versatile component that allows me to input the href, target, and rel attributes, along with specifying the FontAwesome Icon I want to utilize. My goal is to be able to pass multiple icons into this list, which will then ...

Combining namespaces in Typescript declaration files

Currently, I am attempting to combine namespaces from d.ts files. For example, when I attempt to merge namespaces in a single file, everything works as expected. declare namespace tst { export interface info { info1: number; } var a: ...

What is the best way to ensure that each service call to my controller is completed before proceeding to the next one within a loop in Angular?

Calling an Angular service can be done like this: this.webService.add(id) .subscribe(result => { // perform required actions }, error => { // handle errors }); // Service Definition add(id: number): Observable < any > { retu ...

Using a React component with Material-UI style classes in TypeScript is not possible

Recently delving into react, I've embarked on a learning project utilizing typescript 3.7.2 alongside material-ui 4.11.0 and react 16.13.1. Initially, I structured my page layouts using functional components, but upon attempting to switch them to clas ...

The function __WEBPACK_IMPORTED_MODULE_3_ionic_native__.a.open is returning an error

Is there a way to troubleshoot and resolve the following error: WEBPACK_IMPORTED_MODULE_3_ionic_native.a.open is not a function while utilizing the NishanthKabra/Ionic2_GoogleCalendar solution. I am interested in integrating Google Calendar into my Io ...

React.js: You cannot call this expression. The type 'never' does not have any call signatures

Could someone help me troubleshoot the error I'm encountering with useStyles? It seems to be related to Typescript. Here's the line causing the issue: const classes = useStyles(); import React from "react"; import { makeStyles } from & ...

What is the best way to include a non-data custom attribute in a TSX template without any value?

Currently, I am working on a React component with Typescript. The initial code looks like this.... const NameFormatter = React.createClass({ render() { return ( <div> <div className="dataset-name"> ...