
Strictly omitting generic component props
Strictly omitting generic component props êŽë š
Thereâs just one thing Iâm not particularly comfortable with: color turns out to also be a valid attribute for numerous HTML tags, as was the case pre-HTML5. So, if we removed color from our type definition, itâll be accepted as any valid string.
See below:
type TextProps<C extends React.ElementType> = {
as?: C;
// remove color from the definition here
};

Now, if you go ahead to use Text as before, itâs equally valid:
<Text color="violet">Hello world</Text>
The only difference here is how it is typed. color is now represented by the following definition:
color?: string | undefined

Again, this is NOT a definition we wrote in our types!
This is a default HTML typing, where color is a valid attribute for most HTML elements. See this Stack Overflow question for some more context.
Two potential solutions
Now, there are two ways to go here. The first one is to keep our initial solution, where we explicitly declared the color prop:
type TextProps<C extends React.ElementType> = {
as?: C;
color?: Rainbow | "black"; // đ look here
};
The second option arguably provides some more type safety. To achieve this, you must realize where the previous default color definition came from: the React.ComponentPropsWithoutRef<C>. This is what adds other props based on what the type of as is.
So, with this information, we can explicitly remove any definition that exists in our component types from React.ComponentPropsWithoutRef<C>.
This can be tough to understand before you see it in action, so letâs take it step by step.
React.ComponentPropsWithoutRef<C>, as stated earlier, contains every other valid prop based on the type of as, e.g., href, color, etc., where these types have all of their own definitions, e.g., color?: string | undefined, etc.:

ComponentPropsWithoutRef TypeIt is possible that some values that exist in React.ComponentPropsWithoutRef<C> also exist in our component props type definition. In our case, color exists in both!

ComponentPropsWithoutRef And TextPropsInstead of relying on our color definition to override whatâs coming from React.ComponentPropsWithoutRef<C>, we will explicitly remove any type that also exists in our component types definition.

ComponentPropsWithoutRefSo, if any type exists in our component types definition, we will explicitly remove those types from React.ComponentPropsWithoutRef<C>.
Removing types from React.ComponentPropsWithoutRef<C>
Hereâs what we had before:
type Props <C extends React.ElementType> =
React.PropsWithChildren<TextProps<C>> &
React.ComponentPropsWithoutRef<C>
Instead of having an intersection type where we add everything that comes from React.ComponentPropsWithoutRef<C>, we will be more selective. We will use the Omit and keyof TypeScript utility types to perform some TypeScript magic.
Take a look:
// before
type Props <C extends React.ElementType> =
React.PropsWithChildren<TextProps<C>> &
React.ComponentPropsWithoutRef<C>
// after
type Props <C extends React.ElementType> =
React.PropsWithChildren<TextProps<C>> &
Omit<React.ComponentPropsWithoutRef<C>, keyof TextProps<C>>;
This is the important bit:
Omit<React.ComponentPropsWithoutRef<C>, keyof TextProps<C>>;
Omit takes in two generics. The first is an object type, and the second is a union of types youâd like to âomitâ from the object type.
Hereâs my favorite example. Consider a Vowel object type as follows:
type Vowels = {
a: 'a',
e: 'e',
i: 'i',
o: 'o',
u: 'u'
}
This is an object type of key and value. Letâs say that I wanted to derive a new type from Vowels called VowelsInOhans.
Well, I do know that the name Ohans contains two vowels, o and a. Instead of manually declaring these:
type VowelsInOhans = {
a: 'a',
o: 'o'
}
I can go ahead to leverage Omit as follows:
type VowelsInOhans = Omit<Vowels, 'e' | 'i' | 'u'>

Omit will âomitâ the e, i and u keys from the object type Vowels.
On the other hand, TypeScriptâs keyof operator works as you would imagine. Think of Object.keys in JavaScript: given an object type, keyof will return a union type of the keys of the object.
Phew! Thatâs a mouthful. Hereâs an example:
type Vowels = {
a: 'a',
e: 'e',
i: 'i',
o: 'o',
u: 'u'
}
type Vowel = keyof Vowels
Now, Vowel will be a union type of the keys of Vowels, i.e.:
type Vowel = 'a' | 'e' | 'i' | 'o' | 'u'
If you put these together and take a second look at our solution, itâll all come together nicely:
Omit<React.ComponentPropsWithoutRef<C>, keyof TextProps<C>>;
keyof TextProps<C> returns a union type of the keys of our component props. This is in turn passed to Omit to omit them from React.ComponentPropsWithoutRef<C>.
Sweet! đș
To finish, letâs go ahead and actually pass the color prop down to the rendered element:
export const Text = <C extends React.ElementType = "span">({
as,
color, // đ look here
children,
...restProps
}: Props<C>) => {
const Component = as || "span";
// đ compose an inline style object
const style = color ? { style: { color } } : {};
// đ pass the inline style to the rendered element
return (
<Component {...restProps} {...style}>
{children}
</Component>
);
};