edoedoedo
edoedoedo

Reputation: 1641

Why keyof T doesn't work on a generic object

I'd like to have a function with a generic type T which must be an object and must have at least a property named code of type string, and then use keyof T to get all object keys.

Why is this invalid?

function TableView<T extends { code: string }>() {
    return forwardRef<HTMLDivElement, { data: T[], columns: Columns<keyof T> }>(
        (props, ref) => {

Error:

TS2344: Type 'keyof T' does not satisfy the constraint 'string'.
Type 'string | number | symbol' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.

where:

export interface Column<T extends string> {
    field: T;
    label: string;
    align?: "left" | "center" | "right"
    format?: "number" | "integer" | "percent" | "timestamp" | "text"
    backgroundColor?: (row: any) => string
}

type Columns<T extends string> = Column<T>[]

export default Columns

Upvotes: 3

Views: 587

Answers (2)

Aleksey L.
Aleksey L.

Reputation: 37918

Theoretically T can have not only string keys (number | symbol are also allowed). If you want to pick only string keys - you can use Extract utility:

const e = Symbol();

type Foo = {
  a: string; // String-like name
  5: string; // Number-like name
  [e]: string; // Symbol-like name
};

type K1 = keyof Foo; // "a" | 5 | typeof e
type K2 = Extract<keyof Foo, string>; // "a"

Playground


Extract<Type, Union> Constructs a type by extracting from Type all union members that are assignable to Union


Another option suggested by @kaya3 is using string & keyof T intersection.

Upvotes: 5

Marty
Marty

Reputation: 299

Just change extends string to extends any. It will give you the same result you're looking for. The problem is that keyof can be typed as more than just a string.

Upvotes: 1

Related Questions