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 onstring
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}
/>
);
}