Home > Mobile >  Define an interface as String or Object in Tyescript
Define an interface as String or Object in Tyescript

Time:08-07

I am trying to define the correct icon prop for the Button component. This prop could be an object, array, and string.

Button.tsx

import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { SizeProp, IconProp } from '@fortawesome/fontawesome-svg-core'

export interface IconType {
  name: IconProp
  size: SizeProp
  color?: string
}

export interface ButtonProps {
  icon: IconType | IconProp
}

const Button: React.FC<ButtonProps> = ({ icon }: ButtonProps) => {
  return (
    <button>
      <FontAwesomeIcon size={icon.size} icon={icon.name || icon} color={icon.color} />
    </button>
  )
}

export default Button

Well, this are possible values for icon prop for the Button component

import { faHome } from '@fortawesome/free-solid-svg-icons'

<Button icon="home" /> // Icon prop as string
<Button icon={["fab", "github"]} /> // Icon prop as array string with only two positions
<Button icon={{ color: "red", size: "2x", name: "home"  }} /> // Icon prop as object with only "color, size, name" properties, color property can to be optional
<Button icon={faHome} /> // Icon prop as imported icon from library fortawesome

Well, the problem is, that I would like to use all the possible values, but I have an error message: Property 'size' does not exist on type 'IconType | IconProp'

It directs me to the IconProp source and notice that it has the following

export type IconPrefix = "fas" | "fab" | "far" | "fal" | "fad";

export interface IconLookup {
  prefix: IconPrefix;
  // IconName is defined in the code that will be generated at build time and bundled with this file.
  iconName: IconName;
}

export type IconName = '500px' | 
  'accessible-icon' | 
  'accusoft' | 
  'home' |
  'acquisitions-incorporated' | ... more icons

type IconProp = IconName | [IconPrefix, IconName] | IconLookup

Well, so, How I can resolve this error? I'm not 100% proficient in typescript yet, I hope to learn a lot by solving this. Thank you very much

CodePudding user response:

When you do:

<FontAwesomeIcon
  size={icon.size}
  icon={icon.name || icon}
  color={icon.color}
/>

...you already assume that icon prop is of type IconType.

But you type that prop as a union of IconType and IconProp. And the latter, as seen in the shown IconProp source, has no size, name nor color properties.

That is what the error message is about: depending on the actual type of icon prop, you may not have the size property available.

This situation is described in the TypeScript documentation about Unions:

TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union string | number, you can’t use methods that are only available on string


If you really want to stick with a "magic" prop that can take different types (hence the union), you will have to discriminate the types to narrow them down, and to be able to work with them separately.

The solution is to narrow the union with code, the same as you would in JavaScript without type annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

For example in your case, we could do something like:

if ("name" in icon && "size" in icon) {
  return (
    <FontAwesomeIcon
      size={icon.size}
      icon={icon.name}
      color={icon.color}
    />
  );
} else {
  return (
    <FontAwesomeIcon
      icon={icon}
    />
  );
}
  • Related