ChatGPT解决这个技术问题 Extra ChatGPT

'string' can't be used to index type '{}'

I have the following React component that generates an HTML Table from an array of objects. The columns that should be displayed are defined through the tableColumns property.

When looping through items and displaying the correct columns I have to use the key property from the tableColumn object ({item[column.key]}) but typescript is generating the following error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'.

What could I do to fix this? I'm lost

How I call the component:

<TableGridView
  items={[
    {
      id: 1,
      name: 'John Doe',
      email: 'john@doe.de'
    },
    {
      id: 2,
      name: 'Lorem ipsum',
      email: 'lorem@ipsum.com',
    }
  ]}
  tableColumns={[
    {
      key: 'id',
      label: 'ID',
    },
    {
      key: 'name',
      label: 'Name',
    }
  ]}
/>

My Component:

export type TableColumn = {
  key: string,
  label: string,
};

export type TableGridViewProps = {
  items: object[],
  tableColumns: TableColumn[]
};

const TableGridView: React.FC<TableGridViewProps> = ({ tableColumns, items }) => {
  return (
    <table>
      <tbody>
        {items.map(item => {
          return (
            <tr>
              {tableColumns.map((column, index) => {
                return (
                  <td
                    key={column.key}
                    className="lorem ipsum"
                  >
                    {item[column.key]} // error thrown here
                  </td>
                );
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

J
Jonas Wilms
  items: object[],

While technically it is a JavaScript object, the type can be better. For Typescript to correctly help you identify mistakes when accessing objects properties, you need to tell it the exact shape of the object. If you type it as object, typescript cannot help you with that. Instead you could tell it the exact properties and datatypes the object has:

  let assistance: { safe: string } = { safe: 1 /* typescript can now tell this is wrong */ };
  assistance.unknown; // typescript can tell this wont really work too

Now in the case that the object can contain any sort of key / value pair, you can at least tell typescript what type the values (and the keys) have, by using an object index type:

 items: {
   [key: string]: number | string,
  }[]

That would be the accurate type in the case given.


I'm sorry, but your answer doesn't explain the issue, it just states it's bad. Please consider adding an explanation. One would expect to be able to use a string index on an object, without having to explicitely define it.
@maksion one would expect ... I wouldn't? How is Typescript supposed to know what type object[string] is of? It is any, and thus it's a bad type.
THANK YOU FOR THIS! and pls forgive my nit pic: items: { [key: string]: number | string; // <-- }[];
The type of the index into an object can only be string (correct me if I'm wrong), so it does seem superfluous.
@papiro no, also symbols (and numbers, from typescripts perspective)
n
notacorn

If it's a pedantic javascript object that doesn't make sense to create type definitions per field for, and doesn't make sense as a class definition, you can type the object with any and typescript will let you index however you want.

ex.

//obviously no one with two brain cells is going to type each field individually
let complicatedObject: any = {
    attr1: 0,
    attr2: 0,
    ...
    attr999: 0
}

Object.keys(complicatedObject).forEach(key => {
    complicatedObject[key] += 1;
}

Downvote because of really ugly TypeScript workaround.
Upvote because Typescript is often useful but sometimes gets in the way like the OP's issue. You could spend all day trying to type every last thing (and not deliver anything) or take a reasonable middle road of "use it when it helps, makes sense". Remember in the end, at runtime it's JavaScript regardless of how cute you get with the type system and your type definitions might not actually reflect reality of what is running through the code (especially when an API call is involved!)
This literally doesn't work if your keys are typed.
A
Alex Mckay

Use Generics

// bad
const _getKeyValue = (key: string) => (obj: object) => obj[key];

// better
const _getKeyValue_ = (key: string) => (obj: Record<string, any>) => obj[key];

// best
const getKeyValue = <T extends object, U extends keyof T>(key: U) => (obj: T) =>
      obj[key];

Bad - the reason for the error is the object type is just an empty object by default. Therefore it isn't possible to use a string type to index {}.

Better - the reason the error disappears is because now we are telling the compiler the obj argument will be a collection of string/value (string/any) pairs. However, we are using the any type, so we can do better.

Best - T extends empty object. U extends the keys of T. Therefore U will always exist on T, therefore it can be used as a look up value.

Here is a full example:

I have switched the order of the generics (U extends keyof T now comes before T extends object) to highlight that order of generics is not important and you should select an order that makes the most sense for your function.

const getKeyValue = <U extends keyof T, T extends object>(key: U) => (obj: T) =>
  obj[key];

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "John Smith",
  age: 20
};

const getUserName = getKeyValue<keyof User, User>("name")(user);

// => 'John Smith'

Don't use object as a type. The object type is currently hard to use (see this issue). Consider using Record<string, unknown> instead, as it allows you to more easily inspect and use the keys.eslint@typescript-eslint/ban-types)
how do we also specify that T[U] is of type string?
K
Kraken

First of all, defining the type of items as an array of objects is a bad idea, as it defeats the purpose of Typescript. Instead define what type of objects the array will contain. I would do it this way:

type Item = {
  id: number,
  name: string,
  email: string,
}

export type TableGridViewProps = {
  items: Item[],
  tableColumns: TableColumn[]
};

After that if you're absolutely sure that the key would exist in item, you can do the following to tell Typescript that you are accessing a valid index.

<td
  key={column.key}
  className="lorem ipsum"
>
  {item[column.key as keyof typeof Item]}
</td>

Why do you think it "defeats the purpose of typescript?"
@Kraken maybe I phrased it wrongly, but what I'm trying to say is as a good practice if you already know the shape of the object, you shouldn't just make it object just for a quick workaround. Doing so you'll sacrificing alot of the benefits that comes with Typescript
ah I see. I think I can clarify.
Considering this is a generic table component where the keys are defined per column in the props, it's unlikely that the shape of each item is known beforehand, or at the very least the same each time. Might consider typing the item as { [key: string]: string | number } instead if the item shape is agnostic.
The "keyof" keyword did the trick in my case, thanks!
B
Baraka Ally

try adding type any[]

items:any[]