skip to Main Content

I’ve got the following working and I’m trying to convert it to Typescript but whilst I somewhat understand the error, I’m not sure how to go about actually fixing it.

I am currently allowing a component to have a prop of "icon" which is a string and this string is then translating to a component name to be loaded.

icons/index.ts

import { BellIcon } from "./BellIcon";

export const iconMap = {
  BellIcon
};

iconButton.tsx

import { iconMap } from "./icons";

type IconButtonProps = {
  icon: string;
  label?: string;
  direction?: string;
  onClick: () => void;
};

The offending line:

const IconElement = iconMap[icon];

This is what I get as the two errors:

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

Further down the page then for context I am calling the following to get the dynamic component loaded:

<IconElement />

I’ve tried setting various types but can’t find anything that solves the problem, was more trying to guess and I’d actually like to understand the issue.

2

Answers


  1. You need to change the type of the icon prop to only accept keys of the iconMap object.

    import { iconMap } from "./icons";
    
    type IconButtonProps = {
      icon: keyof typeof iconMap;
      label?: string;
      direction?: string;
      onClick: () => void;
    };
    
    Login or Signup to reply.
  2. @emeraldsanto’s answer is correct, but to add some more detail:

    When you define an object like

    const MyMap = {
      foo: 'bar'
    };
    

    Typescript takes a strict approach w/r/t keys and infers the type as

    {
      foo: string;
    }
    

    This means that only the key "foo" is accepted as a valid property in this map. You can override this by setting the type explicitly:

    const MyMap: {[key: string]: string} = {
      foo: 'bar'
    };
    

    or

    const MyMap: Record<string, string> = {
      foo: 'bar'
    };
    

    (These have some subtle differences, but in this case will work the same.) Now TS knows that any string is a valid key for MyMap.

    To go stricter, you can use as const to indicate that your object should be treated as frozen:

    const MyMap = {
      foo: 'bar'
    } as const;
    

    Now TS infers the type in the strictest way:

    {
      readonly foo: 'bar'
    };
    

    This can be beneficial, as now MyMap.foo can be used in contexts that only accept "bar", not any string, and TS will give you an error if you try to compare to something it knows is incorrect, like if (MyMap.foo === "spam").

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search