What is the method for getting TypeScript to type check object literals with dynamic keys?

I'm grappling with the concept of how TypeScript handles type checking when utilizing dynamic keys in object literals. Let's consider these two functions that produce a duplicate of an object:

type Foo = {
  a: number;
  b: number;
};

const INIT_FOO: Foo = { a: 0, b: 0 };

function test1(k: keyof Foo) {
  const f: Foo = { ...INIT_FOO, [k]: true };
  return f
}

function test2(k: keyof Foo) {
  const f: Foo = { ...INIT_FOO };
  f[k] = true;
  return f
}

Surprisingly, the TypeScript compiler will only identify errors within function test2, neglecting those present in function test1.

Why does the compiler fail to flag an error in function test1 even though it contains an obvious mistake?

Answer №1

This issue has been identified and reported on the Microsoft/TypeScript GitHub repository under the reference number microsoft/TypeScript#38663. It occurs due to computed properties where the key is a union of string literals, causing them to be widened to a `string` index signature. While this behavior may not be technically incorrect, it lacks specificity to be useful. Some consider it a bug, as referenced in microsoft/TypeScript#13948, while others see it as a design limitation, discussed in microsoft/TypeScript#21030. Regardless, this is the current behavior within the language.

Once the computed property is broadened to an index signature, detecting the bug becomes challenging. Index signatures are not deemed excess properties; hence, a type like `{a: number, b: number} & {[k: string]: boolean}` (which closely resembles the situation) can be assigned to `{a: number, b: number}` without raising an error. However, in reality, the assignment equates to assigning a `boolean` value to either the `a` or `b` property. The compiler overlooks this due to the widening of the index signature.


To address this issue besides being cautious, you can manually create a function that generates the expected type when dealing with computed properties of union key types:

function computedProp<K extends PropertyKey, V>(
  key: K, val: V
): K extends any ? { [P in K]: V } : never {
  return { [key]: val } as any;
}

The function returns a distributive conditional type resulting in a union of object types. For instance:

const example = computedProp(Math.random() < 0.5 ? "a" : "b", true);
// const example: { a: boolean; } | { b: boolean; }

The union `{a: boolean} | {b: boolean}` accurately represents the intended type of `{[k]: true}`. By using this approach, you'll receive the anticipated error message:

function test1(k: keyof Foo) {
  const f: Foo = { ...INIT_FOO, ...computedProp(k, true) }; // error!
  //    ~
  // '{ a: boolean; b: number; } | { b: boolean; a: number; }' is not assignable to 'Foo'.
  return f
}

While utilizing a function instead of directly applying a computed property may not be ideal, it allows for maintaining a degree of type safety if required.

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

The click event triggered by the onclick clone/function may not always activate the click handler

As a newcomer in the JavaScript domain, I am encountering an issue where the first clone created after clicking 'add more' does not trigger my click me function. However, every subsequent clone works perfectly fine with it. What could be causing ...

Determining whether an option value has been selected in Angular

I am working on a template that includes mat-autocomplete for element searching, with individual option elements displayed. I am trying to implement logic where if an element is selected, the input should be disabled. How can I determine if a specific elem ...

Nullable Object in Vue 3 Composition API

I am utilizing the Vue 3 Composition api along with Typescript to create pinch zoom functionality using the HammerJS package. In my Vue application, I am attempting to replicate a functional example implemented in JavaScript from CodePen: https://codepen. ...

Issue with Vue plugin syntax causing component not to load

I'm facing an issue with a Vue plugin that I have. The code for the plugin is as follows: import _Vue from "vue"; import particles from "./Particles.vue"; const VueParticles = (Vue: typeof _Vue, options: unknown) => { _Vue. ...

Ensure that missing types are included in a union type following a boolean evaluation

When working with typescript, the following code will be typed correctly: let v: number | null | undefined; if(v === null || v === undefined) return; // v is now recognized as a `number` const v2 = v + 2; However, if we decide to streamline this process ...

What steps can I take to set a strict boundary for displaying the address closer to the current location?

While the autocomplete feature works perfectly for me, I encountered an issue where it suggests directions away from my current location when I start typing. I came across another code snippet that uses plain JavaScript to solve this problem by setting bou ...

Tips for formatting dates in Angular 6

I am currently working on a function that displays real-time dates based on user input. Currently, when the user enters the input, it is displayed in the front end as follows: 28.10.2018 10:09 However, I would like the date to change dynamically based on ...

Error: The template is unable to parse due to unrecognized element 'mat-label'

When attempting to write about the datepicker, I encountered the following error: Uncaught Error: Template parse errors: 'mat-label' is not a recognized element: If 'mat-label' is an Angular component, please ensure that it is part o ...

Is there a way to personalize a bootstrap style (btn-link) without being able to modify the button element directly?

I am facing a challenge with a button that is generated by a library (specifically the ngx-bootstrap accordion component) and I do not have direct access to it in my HTML file. Despite being able to inspect the element using Chrome's Inspector, any CS ...

Having trouble displaying nested routes in Angular within the view

I'm encountering some issues with child/nested routes in Angular 4. In the app.module.ts file, my imports statement looks like this: RouterModule.forRoot([ { path: 'templates', component: TemplateLandingC ...

There are no matching overloads in React for this call

Below is an error message in the code. It seems to be related to the usage of <IHistorical[]> in useQuery, but unfortunately, I haven't found a solution for it yet. Overload 1 of 2, '(props: Props | Readonly<Props>): ReactApexChart& ...

Ensuring Proper Implementation of Tailwind CSS Styles on Next.js Image Component with Relative Positioning

Having some challenges while attempting to integrate the Next.js Image component into my React project alongside Tailwind CSS. Specifically, I'm facing issues with applying relative positioning and other styles like objectFit. It seems that the root c ...

Encountering difficulties compiling TypeScript on the Heroku platform

Attempting to deploy a TypeScript Node.js application using Express on Heroku is causing an error. The code is successfully pushed up, Heroku installs the dependencies, and then runs `tsc`, but crashes with `src/controller/adminTypes.ts:3:34 - error TS2307 ...

Managing Angular routing: selectively updating named outlets without reloading submodules

My routing configuration currently reloads Module2 ListComponent on every routing event. However, I want to prevent the list from reloading when a user clicks on a list item within ListComponent. Specifically, when navigating from module2/route1 to module ...

What are the steps to editing and removing items from the list?

I'm currently developing my own client-server application. I have created a basic HTML structure and utilized methods from previous exercises to add items to the server without involving any database operations yet. In the service class, I implemented ...

Is it necessary to disrupt the promise chain in order to pass arguments through?

As a newcomer to nodejs and promises, I am facing a challenge in passing arguments into a callback function within my promise chain. The scenario is as follows: var first = function(something) { /* do something */ return something.toString(); } var second ...

When using Ionic 3 on an Android device, the keyboard is causing the tabs and app content to shift upwards

I'm currently working on an Ionic 3 app and encountering a problem. Whenever I click on the search function, the keyboard opens and pushes all the content of the app to the top. https://i.sstatic.net/GaPW8.png https://i.sstatic.net/7d6Fm.png Here i ...

Import reactjs modules without the need for Browserify, Webpack, or Babel

I am attempting to set up a TypeScript HTML application in Visual Studio. My goal is to incorporate reactjs v0.14.7 without relying on tools like Browserify. But, how can I utilize the react-dom module in this scenario? Let's set aside TypeScript fo ...

Encountering the error message "Error: 'preserveValueImports' is an unknown compiler option" while setting up a SvelteKit project

https://i.stack.imgur.com/fnidk.png Every time I set up a new sveltekit project using TypeScript, I keep encountering the error "Unknown compiler option 'preserveValueImports'.ts" in the tsconfig.json file. The error shows up above the line wher ...

What sets a module apart from a script?

As I delve into the depths of TypeScript documentation to grasp the concept of modules, particularly ES6 modules, I stumbled upon some interesting insights. typescript-modules - this documentation talks about typescript modules and highlights an important ...