3
\$\begingroup\$

(irrelevant code removed for demo purposes)

I am new to using TypeScript and I have this React component which has an object literal inside the component, which I'm then accessing using a computed property key. Everything works as expected but I was getting the following TS error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ keyword: string; hex: string; rgb: string; }'.
No index signature with a parameter of type 'string' was found on type '{ keyword: string; hex: string; rgb: string; }'.ts(7053)

I managed to get rid of the error by adding the : any to the formats object, though I think this is not the best solution?

interface Props {
  keyword: string;
  hex: string;
  rgb: string;
  copyFormat: string;
}

export default ({ keyword, hex, rgb, copyFormat }: Props) => {
  const formats: any = {
    keyword,
    hex: hex.toUpperCase(),
    rgb: `rgb(${rgb})`,
  };

  return <p>{formats[copyFormat]}</p>;
};
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You very rarely need to use any in TypeScript because its type-system is so expressive.

The problem is that copyFormat is specified to be any string.

What would happen if you passed "blahblahblah" as copyFormat? Your types currently allow this!. Then, your code would attempt to do formats["blahblahblah"], but .blahblahblah is not a member of the formats object.

You need a more precise type for copyFormat. You intended the caller to only pass one of "keyword", "hex", or "rgb". You can make a type that is just those three values like so:

interface Props {
  keyword: string;
  hex: string;
  rgb: string;
  copyFormat: "keyword" | "hex" | "rgb";
}

It looks like Props really holds two things; it holds the actual data, and then a separate argument controlling what you read from it. It might make sense to split it into two arguments:

export default ({ keyword, hex, rgb }: Props, copyFormat: "keyword" | "hex" | "rgb") => {
  const formats = {
    keyword,
    hex: hex.toUpperCase(),
    rgb: `rgb(${rgb})`,
  };

  return <p>{formats[copyFormat]}</p>;
}

If you don't like typing out all of the possible key options, you could instead use the keyof type operator to say that copyFormat is any valid key in the Props type. TypeScript will see that all of the properties are strings and correctly infer the resulting type of formats[copyFormat] is thus a string.

interface Props {
  keyword: string;
  hex: string;
  rgb: string;
}

export default ({ keyword, hex, rgb }: Props, copyFormat: keyof Props) => {
  const formats = {
    keyword,
    hex: hex.toUpperCase(),
    rgb: `rgb(${rgb})`,
  };

  return <p>{formats[copyFormat]}</p>;
};
```
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the help, Curtis. I utilized your tips and ended up creating a reusable type interface (which is used in a few places throughout the app): export interface ColorProps { keyword: string; hex: string; rgb: string; }. I then extended that interface and used the keyof ColorProps as the type for copyFormat: interface Props extends ColorProps { copyFormat: keyof ColorProps; onClick: (value: string) => void; }. Hope this is a valid way to do things! \$\endgroup\$ Commented Jun 23, 2019 at 9:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.