Home > Enterprise >  Typescript: How to define conditional typescript props to access object in React?
Typescript: How to define conditional typescript props to access object in React?

Time:09-26

I am trying to use React props as a key to access an object. Based on whether the prop type is "solid" or "outline", will determine what data I use. The object keys within the nested "solid" and "outline" object are not the same so Typescript complains because it says it may not be able to access that key. How do you make typescript happy?

The code works functionally, but not sure how to get rid of the error.

const icons = {
  outline: {
    default: "x1",
    icon1: "icon1"
  },
  solid: {
    default: "x2",
    icon2: "icon2"
  }
} as const;

// If type is solid, you can only choose a solid iconName
// If type is outline, you can only choose an outlined iconName
type ConditionalProps =
  | { type: "solid"; iconName: keyof typeof icons.solid }
  | { type: "outline"; iconName: keyof typeof icons.outline };

const App = ({type = "outline", iconName = "default"}: ConditionalProps) => {

  // Typescript doesn't complain here
  const text1 = icons[type];
  // TSERROR: Typescript complains here
  const text2 = icons[type][iconName];

  return (
    <div>
      <h1>The chosen icon is...</h1>
      <h2>{Object.keys(text1)}</h2>
      <h2>{text2}</h2>
    </div>
  );
}

export default App;

The typescript error is:

// TSERROR
Element implicitly has an 'any' type because expression of type
'"icon2" | "icon1" | "default"' can't be used to index type 
'{ readonly default: "defaultOutline"; readonly icon2: "icon2"; } 
| { readonly default: "defaultSolid"; readonly icon1: "icon1"; }'. 
Property 'icon2' does not exist on type 
'{ readonly default: "defaultOutline"; readonly icon2: "icon2"; } 
| { readonly default: "defaultSolid"; readonly icon1: "icon1"; }'.ts(7053)

Codesandbox Link

CodePudding user response:

Short answer: you can't. At least, safely. Well, kinda.

If you want to have some type safety, you first need to narrow down the union for each case, like so:

function matchIcon(props: ConditionalProps) {
  if (props.type === "outline") {
    return icons[props.type][props.iconName];
  }

  if (props.type === "solid") {
    return icons[props.type][props.iconName];
  }
}

This is somewhat redundant, but it's safe.

The alternative is to suppress the error, but make the code less safe:

  const text2 =
    icons[props.type][
      props.iconName as keyof typeof icons.solid & keyof typeof icons.outline
    ];

Because icons[props.type] could be both icons.outline and icons.solid, we need to pass a value of iconName that satisfy both cases at the same time. keyof typeof icons.solid & keyof typeof icons.outline is just a subset of the possible values, that if provided for both the outline or solid, would still work. We are mostly lying to the compiler.

  • Related