The index type in TypeScript's keyof function is overly broad

Sorry if this question has been addressed before, but I'm having trouble finding the right search terms. Feel free to correct my question if necessary.

This is what I have:

type RowData = Record<string, unknown> & {id: string}; 


type Column<T extends RowData, K extends keyof T> = {  
  key: K; 
  action: (value: T[K], rowData: T) => void;
}

type RowsAndColumns<T extends RowData> = {
  rows: Array<T>; 
  columns: Array<Column<T, keyof T>>;
}

I want TypeScript to automatically infer the types of the action functions based on the structure of the rows and column keys:

For example:

function myFn<T extends RowData>(config: RowsAndColumns<T>) {

}

myFn({
  rows: [
    {id: "foo", 
      bar: "bar", 
      bing: "bing", 
      bong: {
        a: 99, 
        b: "aaa"
      }
    }
  ], 
  columns: [
    {
      key: "bar", 
      action: (value, rowData) => {
          console.log(value);
      }
    },
     {
      key: "bong", 
      action: (value, rowData) => {
          console.log(value.a); //Property 'a' does not exist on type 'string | { a: number; b: string; }'.

      }
    }
  ]
}); 

Playground Link

The issue here is that TypeScript seems to be inferring the type of value (value: T[K]) as 'the types accessible by all keys of T' rather than just the key provided in the column object.

Why is TypeScript behaving like this, and how can it be resolved?

A helpful response would involve defining specific terms and concepts.

It seems like modifying K extends keyof T to allow for 'K being a single fixed key from T' could be a solution.

Answer №1

If you are anticipating the keys of T to consist of a combination of specific strings like

"bong" | "bing" | ...
(rather than just string), then you can define a type that is itself a blend of Column<T, K> for each key K in keyof T.

I typically achieve this by immediately accessing (lookup) a mapped type:

type SomeColumn<T extends RowData> = {
  [K in keyof T]-?: Column<T, K>
}[keyof T]

Alternatively, you can accomplish this using distributive conditional types:

type SomeColumn<T extends RowData> = keyof T extends infer K ?
  K extends keyof T ? Column<T, K> : never : never;

In either approach, your RowsAndColumns type would then incorporate SomeColumn instead of Column:

type RowsAndColumns<T extends RowData> = {
  rows: Array<T>;
  columns: Array<SomeColumn<T>>;
}

This modification enables your intended use case to operate seamlessly without any compilation errors:

myFn({
  rows: [
    {
      id: "foo",
      bar: "bar",
      bing: "bing",
      bong: {
        a: 99,
        b: "aaa"
      }
    }
  ],
  columns: [
    {
      key: "bar",
      action: (value, rowData) => {
        console.log(value);
      }
    },
    {
      key: "bong",
      action: (value, rowData) => {
        console.log(value.a);
      }
    },
  ]
});

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

Why aren't the child route components in Angular 6 loading properly?

I have encountered a small problem. I developed an app using Angular 6 with children routes implemented like this: { path:'pages', component:DatePagesComponent, children : [ {path:'404', component:Error404C ...

Passing an array from a parent component to a child component in Angular

Just to give you some background, I only started my Angular learning journey about a week ago, so feel free to start from the very beginning. So, how can this be achieved? Inside app.component.ts, there is a standard array that needs to be accessible by m ...

Modifying the return type of an observable using the map operator

I have been investigating how to modify the return type of an Observable. My current framework is Angular 5. Let's take a look at this example: public fetchButterflyData(): Observable<Butterfly[]> { return http.get<Larva[]>('u ...

Keep the code running in JavaScript even in the presence of TypeScript errors

While working with create-react-app and typescript, I prefer for javascript execution not to be stopped if a typescript error is detected. Instead, I would like to receive a warning in the console without interrupting the UI. Is it feasible to adjust the ...

Is there a method to create a typecheck for hasOwnProperty?

Given a certain interface interface Bar { bar?: string } Is there a way to make the hasOwnProperty method check the property against the defined interface? const b: Bar = { bar: 'b' } b.hasOwnProperty('bar') // works as expected b. ...

I'm facing challenges in getting my server action to trigger. The error message that keeps popping up is unexpected submission of a React form

I have been working on developing a registration form using Next.js, react-hook-form, and Zod. Here is the code snippet for the form component: 'use client'; import { z } from "zod"; import { useRef } from "react"; import { u ...

Tips for transforming a string into a variable within an Angular framework

I'm working with a JSON object retrieved from an API let arr = [{"name": 'abc',"age": '23'},{"name": 'qwe',"age": '37'},{"name": 'wqewqe',"age&quo ...

How do you properly include a new property in an Object using Typescript?

I am currently delving into the world of typescript. After exploring various sources like this one and that one, as well as trying out multiple solutions, I have encountered a challenge. I have a variable named incomingArticleObject which needs to be of ty ...

Having difficulty executing the Cypress open command within a Next.js project that uses Typescript

I'm having trouble running cypress open in my Next.js project with Typescript. When I run the command, I encounter the following issues: % npm run cypress:open > [email protected] cypress:open > cypress open DevTools listening on ws: ...

The httpClient post request does not successfully trigger in an angular event when the windows.unload event is activated

Is there a way to send a post request from my client to the server when the user closes the tab or browser window? I have tried using the 'windows.unload'or 'windows.beforeunload' event, but the call doesn't seem to be successful a ...

Managing a scenario with a union type where the value can be retrieved from one of two different functions

There are two similar methods that I want to refactor to eliminate redundant code. The first function returns a single element, while the second function returns multiple elements: //returns a single element const getByDataTest = ( container: HTMLElement ...

Determining the height of dynamically rendered child elements in a React application

Looking for a way to dynamically adjust the heights of elements based on other element heights? Struggling with getting references to the "source" objects without ending up in an infinite loop? Here's what I've attempted so far. TimelineData cons ...

Utilizing TypeScript: Importing a typed module within a d.ts file (from an npm package)

I am currently working on integrating a definition file into an npm package that has dependencies on React. The specific library in question can be found at https://github.com/eiriklv/react-masonry-component. In my TypeScript project, I have successfully ...

Show the current time using Moment.js

I am currently working on developing a clock component that displays the current time in real-time. Issue: The initial time is correctly displayed when the page loads (HH:mm A), but the clock does not update dynamically. clock.component.ts : import { ...

Using MUI-X autocomplete with TextField disables the ability to edit cells

When I double-click on a cell, everything works fine. However, after the second click to start editing the textfield, the cell stops editing. I can still choose an option though, so I believe the issue lies somewhere in the textField component, possibly i ...

How to designate a try / catch block as asynchronous in TypeScript / JavaScript?

I am facing an issue where the code is not entering the catch block in case of an error, try { this.doSomething(); } catch (error) { console.error(error); } This problem occurs when "doSomething" returns a promise or runs asynchronous code. doSome ...

The process of setting up a dynamic cursor in ReactFlow with Liveblocks for precise zooming, panning, and compatibility with various screen resolutions

The challenge lies in accurately representing the live cursor's position on other users' screens, which is affected by differences in screen resolutions, zoom levels, and ReactFlow element positioning as a result of individual user interactions. ...

The 'this' context setting function is not functioning as expected

Within my Vue component, I am currently working with the following code: import Vue from 'vue'; import { ElForm } from 'element-ui/types/form'; type Validator = ( this: typeof PasswordReset, rule: any, value: any, callback: ...

Fixing prop passing issues in Vue.js components to prevent errors

My Vue-cli install with TypeScript is set up to render JSX using a boilerplate. However, I am facing an issue when trying to pass a property to the HelloWorld component. Here's what my code looks like: export default Vue.extend({ name: 'App&apo ...

Comparing ESLint and TSLint: Which One Is Right for You

After looking through numerous sources, I came up empty-handed regarding this particular question. Could anyone provide insight on the drawbacks of using Eslint compared to TsLint? What are the reasons for transitioning to ESLint? ...