Making your component generic with respect to values
and items
, we can add a condition for the items
prop where we first check if keyof Values
is assignable to Items[number]
(all elements of the array as a union). This is only true if Items[number]
contains all members of keyof Values
. TypeScript is also smart enough to infer the type of Items
through the conditional.
function MyComponent<Values extends Record<string, unknown>, Items extends (keyof Values)[]>(props: {
values: Values;
items: keyof Values extends Items[number] ? Items : never;
}) { /* ... */ }
// Type 'string[]' is not assignable to type 'never'.
<MyComponent values={{ dog:true, cat:true }} items={['dog']} />;
// ~~~~~
Playground
If you're feeling extra cheeky, you can replace never
with some helpful error message, perhaps,
items: keyof Values extends Items[number] ? Items : `error: key '${Exclude<keyof Values, Items[number]> & string}' is missing`;
So then, if you don't provide any keys, you get meaningful errors:
// Type 'never[]' is not assignable to type '"error: key 'dog' is missing" | "error: key 'cat' is missing"'.
<MyComponent values={{ dog:true, cat:true }} items={[]} />;
// ~~~~~
Playground