I have a function called myStyles
, which takes a single argument. This argument is an object containing a baseStyles
object and a styleVariants
object. It simply returns the styleVariants
object.
function myStyles<StyleVariants extends StyleVariantsStructure>(params: {
baseStyles: AllowedStyles;
styleVariants: StyleVariants;
}): StyleVariants {
return params.styleVariants;
}
It is called like so:
myStyles({
baseStyles: {
color: 'red',
backgroundColor: 'red',
},
styleVariants: {
color: {
red: {
color: 'red',
backgroundColor: 'red',
},
},
},
});
The baseStyles
object has the type AllowedStyles
, which determines which css properties and values we allow. This works perfectly and we get the correct TS errors if the CSS properties or values are misspelled when calling the myStyles
function e.g.
type AllowedStyles = {
color?: 'red';
backgroundColor?: 'red';
};
The styleVariants
object has the type StyleVariantsStructure
. This essentially determines that it should accept one or many nested objects that contain the AllowedStyles
e.g.
type StyleVariantsStructure = {
[k: string]: {
[k: string]: AllowedStyles;
};
};
In the myStyles
function, we use type inference for the styleVariants
param object. Meaning, we automatically get the exact type of the styleVariants
passed in to the myStyles
function. We then use the styleVariants
type as the return type for the function. This is required for us, because we need to know the exact return type of myStyles
. Here's our function again:
function myStyles<StyleVariants extends StyleVariantsStructure>(params: {
baseStyles: AllowedStyles;
styleVariants: StyleVariants;
}): StyleVariants {
return params.styleVariants;
}
And here's an example of the type inference return type, taken from a
The problem
As you can see, I need to both restrict things being passed in the styleVariants
object, using the StyleVariantsStructure
type e.g. we must only accept AllowedStyles
. However, we also need to use the inferred type from styleVariants
for the return type of the myStyles
function. I have attempted to solve this by using StyleVariants extends StyleVariantsStructure
. This works somewhat, but some Typescript errors aren't highlighted in the styleVariants
argument object when calling myStyles
.
For example, if color
is misspelled, we get no TS error:
However, I do get type suggestions:
And, I do get errors if the value is misspelled:
Essentially, to me, it seems like I need to find an alternate way to use type inference for the styleVariants
object, whilst also restricting it using the StyleVariantsStructure
type. Any help on this would be much appreciated.
Minimal demo of the issue in Typescript playground
CodePudding user response:
Strictly speaking, this looks correct, but TS needs to be coaxed into checking it. For example, wrapping the call in the playground with console.log() generates the typechecking you want.
TS is imperfect as a checker, so it can take some finagling for it to detect the "right" solution in highly complex situations - which this is!
CodePudding user response:
The problem seems to be a limitation of Typescript more than anything else. A workaround, which is a complete solution for us, that I found is the following:
function myStyles<StyleVariants>(params: {
baseStyles: AllowedStyles;
styleVariantKeys: StyleVariants;
styleVariants: StyleVariantsStructure;
}): StyleVariants {
return params.styleVariantKeys;
}
- Add
styleVariantsKey
object to themyStyles
function params - Infer the type from that
- Give
styleVariants
theStyleVariantsStructure
type
This allows you to get type checking as intended, and allows you to return the inferred type from styleVariantsKey
. But of course it means you have to have two of the same object, which in our case isn't too much of a problem because we don't actually need the nested styles object to be returned by myStyles
, just the variant names. So our function call looks like this:
myStyles({
baseStyles: {
color: 'red',
backgroundColor: 'red',
},
styleVariantKeys: {
color: ["red"],
},
styleVariants: {
color: {
red: {
color: 'red',
backgroundColor: 'red',
},
},
},
});