Reputation: 1653
I have a component with a prop that I want to restrict to a selection of strings, so am using a union type, like so:
type HeadingProps = {
level?: 'h1' | 'h2' | 'h3' | 'h4'| 'h5' | 'h6'
}
const Heading: React.FC<HeadingProps> = ({children, level = 'h2'}) => {
return <Box as={level} />
}
This works fine when I use it as so..
<Heading level="h1">Hello, world!</Heading>
But if I try and use it with an array I get an error:
{['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].map((level: string) => (
<Heading level={level}>{level}</Heading>
)}
Type 'string' is not assignable to type '"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | undefined'.ts(2322)
How can I have the type of the prop so that only these strings are valid, but enable a valid string value be passed when the component is used in an array like above?
EDIT This is how the types are currently:
export const HeadingLevels = {
h1: `h1`,
h2: `h2`,
h3: `h3`,
h4: `h4`,
h5: `h5`,
h6: `h6`,
}
export type Level = keyof typeof HeadingLevels
type HeadingProps = BoxProps & {
level?: Level
}
Upvotes: 1
Views: 1381
Reputation: 2057
If you split these types, you can use the union type (level) to annotate level
argument of the mapping function as shown below:
type Level = 'h1' | 'h2' | 'h3' | 'h4'| 'h5' | 'h6'
type HeadingProps = {
level?: Level
}
const Heading: React.FC<HeadingProps> = ({children, level = 'h2'}) => {
return <Box as={level} />
}
{['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].map((level: Level) => (
<Heading level={level}>{level}</Heading>
))}
Upvotes: 0
Reputation: 327724
['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
is inferred by the compiler as type string[]
because often that's the right thing to do, as in
const arr = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; // inferred as string[]
arr.push("h7"); // okay
But in your case you want the compiler to know that the contents of that array are and will always be (for as long as you use it, anyway), the particular string literals you set.
As of TypeScript 3.4, the easiest way to deal with this is to use a const
assertion to tell the compiler that your array is intended to be something from which you are reading particular string literals:
(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const).map((level) => (
<Heading level={level}>{level}</Heading>
))
This should work without error. Note that I removed the type annotation from level
. You don't want to widen level
to string
, since then the compiler can't be sure anymore that it's appropriate. Instead, let the compiler infer the type of level
, which it will do as your union "h1"|"h2"|...|"h6"
, as desired.
Okay, hope that helps; good luck!
Upvotes: 5