1

Using Typescript I'm looking to restrict the keys of a passed object in a React component to the items in an array thats also passed to the React component:

    <MyComponent values={{ dog:true }} items={['dog']} /> // valid

    <MyComponent values={{ dog:true, cat:true }} items={['dog']} /> // invalid - `cat` not in array

Can this be solved with generics?

cafce25
  • 15,907
  • 4
  • 25
  • 31
jigglyT101
  • 984
  • 1
  • 15
  • 33

1 Answers1

1

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

kelsny
  • 23,009
  • 3
  • 19
  • 48