I have the next situation:
const data: Record<string, string> = {
a: '110px',
b: '160px',
};
interface Props {
d?: keyof typeof data[];
}
const t = (d: Props) => 'hi' + d;
t(['a']) // here should be allowed only data keys (a and b)
I want to restrict the t() arguments only to data object keys. I tried as above but i get TS error: Type 'string[]' has no properties in common with type 'Props'.(2559). How to achieve that?
NOTE: i need to do this dynamycly reading the keys of the object.
demo: link
>Solution :
Two answers for you:
-
If by "dynamically" you mean at runtime, no, that’s not possible with TypeScript. TypeScript only works with compile-time information.
-
If by "dynamically" you mean it works even if you change the object literal that creates
data, you can do it with(keyof typeof data)[]and allowing TypeScript to infer the type ofdata(by removing the type annotation on it):const data = { // <== Note no `Record<string, string>` on this a: '110px', b: '160px', }; const t = (d: (keyof typeof data)[]) => 'hi' + d; t(["a", "b"]); // <=== Works as expected t(["x"]); // <=== Error as desiredIf you change the object literal defining
dataso that it (now) has acproperty, the definition oftdoesn’t have to change (playground), it picks that up.As captain-yossarian from Ukraine pointed out in a comment, starting with TypeScript 4.9, if you want to make sure that you can’t accidentally add an entry to
datathat doesn’t have a string value (or even a more specific one, more in a minute), you can use the newsatisfiesoperator. That lets you enforce a limitation on the object’s properties without broadening its type, like this:const data = { a: '110px', b: '160px', } satisfies Record<string, string>; //^^^^^^^^^−−−−−− new TypeScript 4.9 operator const t = (d: (keyof typeof data)[]) => 'hi' + d; t(["a", "b"]); // <=== Works as expected t(["x"]); // <=== Error as desiredWhen you do that, if you try to add
c: 42todata, you’ll get an error from TypeScript (link) because that would mean thatdata‘s type didn’t satisfy the constraintRecord<string, string>. But unlikeas, it won’t narrow the type ofdata, so ourkeyof typeof datastill sees onlydata‘s actual property names as a union ("a" | "b"), not juststring.As he also pointed out, if you want to make sure all of your values in
dataare in the form<number>px, you can use a template literal type likeRecord<string, `${number}px`>withsatisfies(link).