Reputation: 63
Noob with TS here. I am creating a custom Icon component in React and I want to only use Rounded Material UI icons.
I am defining the type like so:
import * as icons from '@material-ui/icons/index'
export type MaterialUiIcon = keyof typeof icons
this works but only gives me typechecking for all material UI icons.
instead this is what I want to do:
const roundedIconsNames = (Object.keys(icons).filter((icon) => icon.includes('Rounded')))
export type RoundedMaterialUiIcon = typeof roundedIconsNames[number]
however RoundedMaterialUiIcon ends up being type string[]
How can I make this work?
Thank you.
Upvotes: 1
Views: 395
Reputation: 33111
You are receiving string[]
because Object.keys
always returns string[]
by design.
I believe it is better to make typeguard and utility function:
import * as icons from '@material-ui/icons/index'
type Icons = typeof icons
export type MaterialUiIcon = keyof Icons
type Prefix<T extends string> = `${string}${T}` | `${string}${T}${string}` | `${T}${string}`
// self explanatory, we have 3 variant of word
type Test1 = Prefix<'Rounded'> // `${string}Rounded` | `${string}Rounded${string}` | `Rounded${string}`
type GetByPrefix<T, P> = T extends P ? T : never;
/**
* This will return only HelloRounded, because union 'HelloRounded' | 'Batman'
* extends `${string}Rounded` | `${string}Rounded${string}` | `Rounded${string}`
*
* WHy extends?
* Because Rounded is at the end
*/
type Test2 = GetByPrefix<'HelloRounded' | 'Batman', Prefix<'Rounded'>> // 'HelloRounded'
/**
* This will return "RoundedWorld" because Rounded is at the beginning
*/
type Test3 = GetByPrefix<'RoundedWorld' | 'Batman', Prefix<'Rounded'>> // "RoundedWorld"
/**
* This is a special syntax for user defined typeguards
* It may help you to narrow the type
* https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
*/
const typeguard = <T extends string,>(tag: string) => (icon: MaterialUiIcon): icon is GetByPrefix<MaterialUiIcon, Prefix<T>> => icon.includes(tag)
const getRounded = <T extends string>(icons: Icons, includes: T) =>
/**
* Object.keys will always return string[], this is by design
* So you need to use type assertion here
*
* Array.prototype.filter accept curried typeguard
* It is a good practive yo use user defined typeguards a a predicate
* callback in array methods
*/
(Object.keys(icons) as Array<MaterialUiIcon>).filter(typeguard<T>(includes))
const result = getRounded(icons, 'Rounded')
Pls, keep in mind, since Icons
have 5K union types, it is not easy task for TS to iterate through all unions.
Upvotes: 1