Assigning fields dynamically based on a generic string union concept

My goal is to create a function that can dynamically add fields and functions to an object based on arguments provided. However, I'm encountering an issue where the function does not recognize the types of these dynamic fields. Here's a simple example that illustrates the problem:

type Extended<Base extends object, Name extends string> =
  Base & Record<Name, string> & Record<`${Name}Something`, boolean>;

const addFields = <Base extends object, Name extends string>(
  obj: Base, names: ReadonlyArray<Name>
): Extended<Base, Name> => {
  return names.reduce((acc, name) => {
    acc[name] = "test"; // error!
    //~~~~~~~
    // Type 'string' is not assignable to type 'Extended<Base, Name>[Name]'.(2322)
    acc[`${name}Something`] = true; // error!
    //~~~~~~~~~~~~~~~~~~~~~
    // Type 'boolean' is not assignable to type 
    // 'Extended<Base, Name>[`${Name}Something`]'.(2322)
    return acc;
  }, { ...obj } as Extended<Base, Name>)
}

const test = addFields({ x: 123 }, ["y"]);
test.x;
test.y;
test.y = "test2";
test.ySomething = false;

TS playground

Is there a way to make TypeScript recognize these dynamic fields within the function properly, or should I approach typing it differently to avoid this issue while still maintaining type safety?

Answer №1

The validation of assignability to properties of intersection types with generic mapped types is not effectively handled by the compiler. This issue has been raised on GitHub, such as the one documented in microsoft/TypeScript#38796, without a clear explanation from the authorities.

It is worth noting that determining such types can be quite challenging even for humans. Consider the following type:

type Extended<Base extends object, Name extends string> =
  Base & Record<Name, string> & Record<`${Name}Something`, boolean>;

In situations where the Name in the type definition is a union of string literal types with one member being equal to another member with "Something" appended to it, unexpected behaviors may occur. For instance:

type Hmm = Extended<{}, "a" | "aSomething">;
// type Hmm = never

When expanded, this results in an intersection that leads to impossible properties, ultimately resulting in the never type. This rationalizes the compiler's complaints about certain assignments.

While the TypeScript team could address these issues in the future, the current workaround is to handle them as missing features.


A suggested workaround involves widening the intersection type to one of its members before accessing the properties. For example:

const addFields = <Base extends object, Name extends string>(
  obj: Base, names: ReadonlyArray<Name>
): Extended<Base, Name> => {
  return names.reduce((acc, name) => {
    const acc1: Record<Name, string> = acc; // okay
    acc1[name] = "test"; // okay
    const acc2: Record<`${Name}Something`, boolean> = acc; // okay
    acc2[`${name}Something`] = true; // okay
    return acc;
  }, { ...obj } as Extended<Base, Name>)
}

By first widening the acc type to

Record<Name, string></code and then to <code>Record<`${Name}Something`, boolean>
, these assignments can be executed without issues. This approach simplifies the evaluation process for the compiler.

Although there may be edge cases where this workaround is not foolproof, it effectively resolves the immediate challenges.

Playground link to code

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

Enhancing DOM Elements in a React Application Using TypeScript and Styled-Components with Click Event

I've been working on an app using React, Typescript, and styled components (still a beginner with typescript and styled components). I'm trying to create a simple click event that toggles between which of the two child components is visible insid ...

When Typecasted in Typescript, the result is consistently returned as "object"

Consider a scenario where there are two interfaces with identical members 'id' and 'name': export interface InterfaceA { id: number; name: string; //some other members } export interface InterfaceB { id: number; nam ...

Error message: Custom binding handler failed: 'Flatpickr' is not a valid constructor

Trying my hand at creating a custom binding handler in knockout for Flatpickr has hit a snag. Upon attempting to use it, an error is thrown: Uncaught TypeError: Unable to process binding "datetimepicker: function (){return startDate }" Message: Flatpickr ...

Determining whether a Typescript AST node represents a javascript native function

How can I determine if an AST node in TypeScript represents a valid JavaScript function, as opposed to a custom method? Here's what I'm thinking: function isJavascriptFunction(node: ts.Node): boolean { // ----- } For instance, given the cod ...

Combining data types in TypeScript (incorporating new keys into an existing keyof type)

If I have a typescript type with keys: const anObject = {value1: '1', value2: '2', value3: '3'} type objectKeys = keyof typeof anObject and I want to add additional keys to the type without manually defining them, how can I ...

In order to set a condition for the mat date picker to display a text box in Angular if the selected date is for someone under 18 years old

I need assistance with displaying a text field based on age validation. The requirement is to show the input field only if the age is less than 18. Below is the code snippet I am currently working with: <form [formGroup]="form"> ...

Accessing the form element in the HTML outside of the form tag in Angular 2

I am attempting to achieve the following: <span *ngIf="heroForm?.dirty"> FOO </span> <form *ngIf="active" (ngSubmit)="onSubmit()" #heroForm="ngForm"> <div class="form-group"> <label for="name">Name</label& ...

Creating custom designs for Material UI components

Although not a major issue, there is something that bothers me. I am currently using react, typescript, and css modules along with . The problem arises when styling material ui components as I find myself needing to use !important quite frequently. Is th ...

Combining normal imports with top-level await: A guide

Is it possible to simultaneously use imports (import x from y) and top-level awaits with ts-node? I encountered an issue where changing my tsconfig.compilerOptions.module to es2017 or higher, as required by top-level awaits, resulted in the following error ...

Integrating Typescript into function parameters

I am attempting to make my function flexible by allowing it to accept either a string or a custom type onPress: (value: string | CustomType)=>void But when I try to assign a string or CustomType, the compiler gives an error saying is not assignable to ...

An issue arises when trying to utilize meta tags in Nuxtjs while incorporating TypeScript into the

When working with Nuxtjs, I encountered an issue regarding my permissionKeys on the page and checking user access in the middleware. Everything runs smoothly when my script language is set to js, but errors arise when set to lang="ts". I tried to find a s ...

Angular's HttpClient is stating that the property '.shareReplay' is not recognized on the type 'Observable'

Excuse me for asking what may seem like a basic question. I'm currently following a tutorial at this link: I have created the Service as shown in the tutorial, but I am getting an error that says Property '.shareReplay' does not exist on ty ...

What is the recommended return type in Typescript for a component that returns a Material-UI TableContainer?

My component is generating a Material-UI Table wrapped inside a TableContainer const DataReleaseChart = (): React.FC<?> => { return ( <TableContainer sx={{ display: 'grid', rowGap: 7, }} > ...

Designing functional components in React with personalized properties utilizing TypeScript and Material-UI

Looking for help on composing MyCustomButton with Button in Material-ui import React from "react"; import { Button, ButtonProps } from "@material-ui/core"; interface MyButtonProps { 'aria-label': string, // Adding aria-label as a required pro ...

The 'filter' attribute is not found in the 'ContextProps' type

I am currently working on a project in Next.js 13 where I am trying to render card items conditionally based on their status. The TypeScript version being used is "5.2.2". However, I encountered an error that says: Property 'filter' does not exis ...

What is the reason for using a string as the index of an array in the following code?

var arrayOfNumbers = [1, 2, 3, 4, 5, 6, 78]; for(var index in arrayOfNumbers){ console.log(index+1); } The result produced by the given code snippet is as follows: 01 11 21 31 41 51 61 What is the reason behind JavaScript treating these ...

Is the parent component not triggering the function properly?

Hey there, I'm working with the code snippet below in this component: <app-steps #appSteps [menuSteps]="steps" [currentComponent]="outlet?.component" (currentStepChange)="currentStep = $event"> <div appStep ...

Error with Chakra UI and React Hook Form mismatched data types

Struggling with creating a form using ChakraUI and React-Hook-Form in TypeScript. The errors seem to be related to TypeScript issues. I simply copied and pasted this code from the template provided on Chakra's website. Here is the snippet: import { ...

Guide for Showing Data from Json Mapper in Angular 5

As a newcomer to Angular5 with TypeScript, I am trying to figure out how to display data from my JSON. I have an API that was created using Java. I have created a JSON Mapper in my Angular code like this : The JSON generated from my Java application looks ...

Filtering database results from an Angular component

I am currently working on an Angular component and I have a result variable in the .ts file that stores data retrieved from the database. My goal is to filter this result variable to display only 20 records and sort them by date either in ascending or de ...