Home > OS >  Type of key attribute in array is not sufficiently strict
Type of key attribute in array is not sufficiently strict

Time:06-18

I believe I've managed to use generics so that the DataPresentation component's searchKey prop can only be one of the values of field in columns

type Props<RowType extends AnyObject, ColType extends string> = {
  columns: ReadonlyArray<{ readonly field: ColType }>;
  rows: RowType[];
  searchKey?: ColType;
};

const DataPresentation = <RowType extends AnyObject, ColType extends string>({
  columns, rows, searchKey,
}: Props<RowType, ColType>) => { ... }

Here I am using the component:

export const playerPropsColumns = [
  { field: 'sportId' }, // todo: get sport name. from backend?
  { field: 'playerName' },
  { field: 'line' },
  { field: 'status' },
] as const;


// inside a component
<DataPresentation
  columns={playerPropsColumns}
  rows={data}
  searchKey={'XXXXX'}
/>

That invalid searchKey passes the type check, but it's more interesting than simply "it's not working." When I hover over searchKey, its type is:

searchKey?: "line" | "sportId" | "playerName" | "status" | "XXXXX" | undefined

and playerPropsColumns's type is

columns: readonly {readonly field: "line" | "sportId" | "playerName" | "status" | "XXXXX"}[]

So it's getting the acceptable values from the playerPropsColumns array, but it's also accepting whatever I feed into searchKey which obviously completely defeats the point of type checking.

What's going on here and how do I fix it?

Note: I will say that all the read-only stuff is so I could use as const in my definition of the columns, so that I could do type FieldType = typeof playerPropsColumns[number]['field'];. However, after setting all this up and using generics I realize I of course didn't need that typedef at all, so maybe I can get rid of the read only stuff, and maybe they're the problem?

CodePudding user response:

What you are observing here is intended behaviour. The readonly modifiers are not the problem here. When you use a generic type for multiple parameters, the types of those parameters will join together in a union.

function test<T extends string>(a: T, b: T) {}

test("a", "b")
// function test<"a" | "b">(a: "a" | "b", b: "a" | "b"): void

So how can you solve this problem?

Instead of using T to store a union of possible values, you could use T to store the whole tuple.

type Props<ColTypes extends readonly { field: string }[]> = {
  columns: ColTypes;
  searchKey?: ColTypes[number];
};

const DataPresentation = <
  ColTypes extends readonly { field: string }[]
>({
  columns, searchKey,
}: Props<ColTypes>) => {}

We can now use ColTypes[number]["field"] to constain the type of the field searchKey.

Playground

CodePudding user response:

You can make SearchKey a separate generic parameter which extends ColType so Typescript knows which is to be inferred as an input type, and which is a constraint.

For example:

(Note I removed rows since it has nothing to do with the actual issue here)

type Props<
  ColType extends string,
  SearchKey extends ColType
> = {
  columns: ReadonlyArray<{ readonly field: ColType }>;
  searchKey?: SearchKey;
};

const DataPresentation = <
  ColType extends string,
  SearchKey extends ColType
>({
  columns,
  searchKey,
}: Props<ColType, SearchKey>) => {
  return <></>
}

Now the example works as you would expect:

export const playerPropsColumns = [
  { field: 'sportId' }, 
  { field: 'playerName' },
  { field: 'line' },
  { field: 'status' },
] as const;

const testA = <DataPresentation
  columns={playerPropsColumns}
  searchKey='line'
/> // fine

const testB = <DataPresentation
  columns={playerPropsColumns}
  searchKey='XXXXX' 
/> // error

See Playground

  • Related